org.apache.flume.sink.kite.KerberosUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.flume.sink.kite.KerberosUtil.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 org.apache.flume.sink.kite;

import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import java.io.File;
import java.io.IOException;
import java.security.PrivilegedExceptionAction;
import org.apache.hadoop.security.SecurityUtil;
import org.apache.hadoop.security.UserGroupInformation;
import org.kitesdk.data.DatasetException;
import org.kitesdk.data.DatasetIOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class KerberosUtil {

    private static final Logger LOG = LoggerFactory.getLogger(org.apache.flume.sink.kite.KerberosUtil.class);

    public static class SecurityException extends RuntimeException {
        private SecurityException(String message) {
            super(message);
        }

        private SecurityException(String message, Throwable cause) {
            super(message, cause);
        }

        private SecurityException(Throwable cause) {
            super(cause);
        }
    }

    public static UserGroupInformation proxyAs(String username, UserGroupInformation login) {
        Preconditions.checkArgument(username != null && !username.isEmpty(),
                "Invalid username: " + String.valueOf(username));
        Preconditions.checkArgument(login != null, "Cannot proxy without an authenticated user");

        // hadoop impersonation works with or without kerberos security
        return UserGroupInformation.createProxyUser(username, login);
    }

    /**
     * Static synchronized method for static Kerberos login. <br/>
     * Static synchronized due to a thundering herd problem when multiple Sinks
     * attempt to log in using the same principal at the same time with the
     * intention of impersonating different users (or even the same user).
     * If this is not controlled, MIT Kerberos v5 believes it is seeing a replay
     * attach and it returns:
     * <blockquote>Request is a replay (34) - PROCESS_TGS</blockquote>
     * In addition, since the underlying Hadoop APIs we are using for
     * impersonation are static, we define this method as static as well.
     *
     * @param principal
     *         Fully-qualified principal to use for authentication.
     * @param keytab
     *         Location of keytab file containing credentials for principal.
     * @return Logged-in user
     * @throws org.apache.flume.sink.kite.KerberosUtil.SecurityException
     *         if login fails.
     * @throws IllegalArgumentException
     *         if the principal or the keytab is not usable
     */
    public static synchronized UserGroupInformation login(String principal, String keytab) {
        // resolve the requested principal, if it is present
        String finalPrincipal = null;
        if (principal != null && !principal.isEmpty()) {
            try {
                // resolves _HOST pattern using standard Hadoop search/replace
                // via DNS lookup when 2nd argument is empty
                finalPrincipal = SecurityUtil.getServerPrincipal(principal, "");
            } catch (IOException e) {
                throw new SecurityException("Failed to resolve Kerberos principal", e);
            }
        }

        // check if there is a user already logged in
        UserGroupInformation currentUser = null;
        try {
            currentUser = UserGroupInformation.getLoginUser();
        } catch (IOException e) {
            // not a big deal but this shouldn't typically happen because it will
            // generally fall back to the UNIX user
            LOG.debug("Unable to get login user before Kerberos auth attempt", e);
        }

        // if the current user is valid (matches the given principal) then use it
        if (currentUser != null) {
            if (finalPrincipal == null || finalPrincipal.equals(currentUser.getUserName())) {
                LOG.debug("Using existing login for {}: {}", finalPrincipal, currentUser);
                return currentUser;
            } else {
                // be cruel and unusual when user tries to login as multiple principals
                // this isn't really valid with a reconfigure but this should be rare
                // enough to warrant a restart of the agent JVM
                // TODO: find a way to interrogate the entire current config state,
                // since we don't have to be unnecessarily protective if they switch all
                // HDFS sinks to use a different principal all at once.
                throw new SecurityException("Cannot use multiple Kerberos principals: " + finalPrincipal
                        + " would replace " + currentUser.getUserName());
            }
        }

        // prepare for a new login
        Preconditions.checkArgument(principal != null && !principal.isEmpty(),
                "Invalid Kerberos principal: " + String.valueOf(principal));
        Preconditions.checkNotNull(finalPrincipal, "Resolved principal must not be null");
        Preconditions.checkArgument(keytab != null && !keytab.isEmpty(),
                "Invalid Kerberos keytab: " + String.valueOf(keytab));
        File keytabFile = new File(keytab);
        Preconditions.checkArgument(keytabFile.isFile() && keytabFile.canRead(),
                "Keytab is not a readable file: " + String.valueOf(keytab));

        try {
            // attempt static kerberos login
            LOG.debug("Logging in as {} with {}", finalPrincipal, keytab);
            UserGroupInformation.loginUserFromKeytab(principal, keytab);
            return UserGroupInformation.getLoginUser();
        } catch (IOException e) {
            throw new SecurityException("Kerberos login failed", e);
        }
    }

    /**
     * Allow methods to act with the privileges of a login.
     *
     * If the login is null, the current privileges will be used.
     *
     * @param <T> The return type of the action
     * @param login UserGroupInformation credentials to use for action
     * @param action A PrivilegedExceptionAction to perform as another user
     * @return the T value returned by action.run()
     */
    public static <T> T runPrivileged(UserGroupInformation login, PrivilegedExceptionAction<T> action) {
        try {
            if (login == null) {
                return action.run();
            } else {
                return login.doAs(action);
            }
        } catch (IOException ex) {
            throw new DatasetIOException("Privileged action failed", ex);
        } catch (InterruptedException ex) {
            Thread.interrupted();
            throw new DatasetException(ex);
        } catch (Exception ex) {
            throw Throwables.propagate(ex);
        }
    }
}