com.cloudera.impala.security.DelegationTokenManager.java Source code

Java tutorial

Introduction

Here is the source code for com.cloudera.impala.security.DelegationTokenManager.java

Source

/**
 * 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);
    }
}