Java tutorial
/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cloudera.impala.security; import java.io.IOException; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.tuple.Pair; import org.apache.hadoop.conf.Configuration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.cloudera.impala.service.ZooKeeperSession; /** * Highest level class that deals with delegation tokens. This class is expected * to run as a singleton on each daemon in a secure cluster. * * Unlike the other classes in this package, this one is not copied from the Hive * project and includes slightly higher APIs and serves as a wrapper for the other * classes. Our other classes should not interact with the other classes in this * package directly. * * Brief primer on Delegation Tokens: Delegation tokens provide an alternative (to * kerberos) way of doing authentication that is well suited for distributed * environments. Authentication is first done via kerberos and then subsequently * can be done using delegation tokens. After establishing the initial kerberos * connection, the client can get a delegation token (getToken()). This token * consists of two parts: * 1. The token identifier (username, seq id, other metadata). There is no secret * in this information but allows a different token per user per application. The * seq id is just a monotonically increasing value. * 2. The token password. This is generated by the server using the identifier * and a secret key (which is periodically rotated and shared by our daemons). * The client receives both and can use it to authenticate with services from * then on. The client can pass the token to anyone else to authenticate as them. * The tokens have nice properties including: cancellation, renewing and expiration * and are persisted by the server in ZK so they stay valid after restarts. * * To authenticate, the client simply provides the token and the server is able to * verify that the password matches. * * This class is thread safe. * * TODO: we need to add the issuing host to the identifier to allow multiple planners * to generate tokens independently. * TODO: does the password have to be persisted? We should just be able to recompute * on the fly using the identifier and secret. Is that true? */ public class DelegationTokenManager { private static final Logger LOGGER = LoggerFactory.getLogger(DelegationTokenManager.class.getName()); private static final long ONE_HOUR = 60 * 60 * 1000; private static final long ONE_DAY = ONE_HOUR * 24; // The interval at which new master keys are generated. public static final String DELEGATION_KEY_UPDATE_INTERVAL_KEY = "recordservice.delegation-key.update-interval"; public static final long DELEGATION_KEY_UPDATE_INTERVAL_DEFAULT = ONE_DAY; // The max duration for a single delegation token. Cannot be renewed beyond this. public static final String DELEGATION_TOKEN_MAX_LIFETIME_KEY = "recordservice.delegegation-token.max-lifetime"; public static final long DELEGATION_TOKEN_MAX_LIFETIME_DEFAULT = 7 * ONE_DAY; // The duration a delegation token is renewed for, when it is renewed. public static final String DELEGATION_TOKEN_RENEW_INTERVAL_KEY = "recordservice.delegation-token.renew-interval"; public static final long DELEGATION_TOKEN_RENEW_INTERVAL_DEFAULT = ONE_DAY; // The interval to check for expired delegation tokens. public static final String DELEGATION_TOKEN_GC_INTERVAL = "recordservice.delegation-token.gc-interval"; private final static long DELEGATION_TOKEN_GC_INTERVAL_DEFAULT = ONE_HOUR; // Singleton instance, created in init(). private static DelegationTokenManager instance_; /** * Creates a singleton instance of the token manager to handle delegation token * requests. * If generatesTokens is true, it means this token manager also needs to be able * to create/renew/cancel tokens, otherwise it can only verify them. * This is expected to be called once in secure clusters. * * If zkSession is not null, the tokens are persisted in ZK. For distributed * deployements, this is required. * * This starts the threads to manage delegation tokens. */ public static void init(Configuration conf, boolean generatesTokens, ZooKeeperSession zkSession) throws IOException { if (instance_ != null) { throw new RuntimeException("init() can only be called once."); } LOGGER.info("Starting token manager."); instance_ = new DelegationTokenManager(conf, generatesTokens, zkSession); LOGGER.info("Token manager started."); } public static DelegationTokenManager instance() { if (instance_ == null) throw new RuntimeException("init() must be called."); return instance_; } // Underlying token manager. private final DelegationTokenSecretManager mgr_; // If true, this manager needs to be able to generate tokens. private final boolean generatesTokens_; protected DelegationTokenManager(Configuration conf, boolean generatesTokens, ZooKeeperSession zkSession) throws IOException { long secretKeyInterval = conf.getLong(DELEGATION_KEY_UPDATE_INTERVAL_KEY, DELEGATION_KEY_UPDATE_INTERVAL_DEFAULT); long tokenMaxLifetime = conf.getLong(DELEGATION_TOKEN_MAX_LIFETIME_KEY, DELEGATION_TOKEN_MAX_LIFETIME_DEFAULT); long tokenRenewInterval = conf.getLong(DELEGATION_TOKEN_RENEW_INTERVAL_KEY, DELEGATION_TOKEN_RENEW_INTERVAL_DEFAULT); long tokenGcInterval = conf.getLong(DELEGATION_TOKEN_GC_INTERVAL, DELEGATION_TOKEN_GC_INTERVAL_DEFAULT); generatesTokens_ = generatesTokens; if (zkSession != null) { ZooKeeperTokenStore store = new ZooKeeperTokenStore(zkSession); mgr_ = new PersistedDelegationTokenSecretManager(secretKeyInterval, tokenMaxLifetime, tokenRenewInterval, tokenGcInterval, store); } else { mgr_ = new DelegationTokenSecretManager(secretKeyInterval, tokenMaxLifetime, tokenRenewInterval, tokenGcInterval); } mgr_.startThreads(); } // Serialized token split into the various parts needed by the client. // TODO: the existing APIs are kind of awkward so we set it up this way. Are we // missing something? public static final class DelegationToken { // Serialized and encoded DelegationTokenIdentifier public final String identifier; // Token<DelegationTokenIdentifier>.getPassword() encoded. Don't log this. public final String password; // Token<DelegationTokenIdentifier>.encodeToUrlString(). Don't log this. public final byte[] token; public DelegationToken(final String id, final String pw, final byte[] t) { identifier = id; password = pw; token = t; } } // Container for user and password. Used during authentication by token. public static final class UserPassword { public final String user; public final String password; public UserPassword(final String u, final String pw) { user = u; password = pw; } } /** * Creates a new serialized delegation token for 'owner'. */ public DelegationToken getToken(final String owner, final String renewer, final String realUser) throws IOException { if (!generatesTokens_) { throw new RuntimeException("This token manager cannot generate tokens."); } return mgr_.getDelegationToken(owner, renewer, realUser); } /** * Renews the token. tokenString is the serialized result from getToken() * Returns the new expiration time (as ms since epoch). */ public long renewToken(final String user, final byte[] tokenString) throws IOException { LOGGER.info("renewToken() user=" + user); if (!generatesTokens_) { throw new RuntimeException("This token manager cannot renew tokens."); } return mgr_.renewDelegationToken(user, new String(tokenString)); } /** * Cancels the token. tokenString is the serialized result from getToken() */ public void cancelToken(final String user, final byte[] tokenString) throws IOException { LOGGER.info("cancelToken() user=" + user); if (!generatesTokens_) { throw new RuntimeException("This token manager cannot cancel tokens."); } mgr_.cancelDelegationToken(user, new String(tokenString)); } /** * Returns the password for identifier. This is used by the server to authenticate * a connection. * tokenString is the entire serialized token (which contains a password that we * ignore). */ public byte[] getPasswordByToken(final byte[] tokenString) throws IOException { DelegationTokenIdentifier id = mgr_.tokenIdentifierFromTokenString(new String(tokenString)); return mgr_.retrievePassword(id); } /** * Returns the user and password for identifier. */ public UserPassword retrieveUserPassword(final String tokenIdString) throws IOException { LOGGER.info("Retrieving token for: " + tokenIdString); byte[] bytes = Base64.decodeBase64(tokenIdString); DelegationTokenIdentifier id = DelegationTokenIdentifier.deserialize(bytes); String pw = DelegationTokenSecretManager.encodePassword(mgr_.retriableRetrievePassword(id)); return new UserPassword(id.getUser().getShortUserName(), pw); } /** * Given the sequence number 'keySeq', return a master key in the ZooKeeper token * store, and the corresponding sequence number to the key. * If the 'keySeq' is negative, this returns the latest master key, and the * corresponding sequence number. Otherwise, the sequence number in the result will * be the same as the input one. * The token manager needs to use ZooKeeper in order to perform this operation. * Otherwise, a RuntimeException will be thrown. */ public Pair<Integer, String> getMasterKey(int keySeq) throws IOException { if (!(mgr_ instanceof PersistedDelegationTokenSecretManager)) { throw new RuntimeException( "getMasterKey() can only be called on " + "PersistedDelegationTokenSecretManager."); } ZooKeeperTokenStore store = ((PersistedDelegationTokenSecretManager) mgr_).getTokenStore(); return store.getMasterKey(keySeq); } }