backtype.storm.security.auth.kerberos.KerberosSaslTransportPlugin.java Source code

Java tutorial

Introduction

Here is the source code for backtype.storm.security.auth.kerberos.KerberosSaslTransportPlugin.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 backtype.storm.security.auth.kerberos;

import java.io.IOException;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.kerberos.KerberosTicket;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginException;
import javax.security.sasl.Sasl;

import org.apache.commons.lang.StringUtils;
import org.apache.thrift.transport.TSaslClientTransport;
import org.apache.thrift.transport.TSaslServerTransport;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.apache.thrift.transport.TTransportFactory;
import org.apache.zookeeper.Login;
import org.apache.zookeeper.server.auth.KerberosName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import backtype.storm.security.auth.AuthUtils;
import backtype.storm.security.auth.SaslTransportPlugin;

public class KerberosSaslTransportPlugin extends SaslTransportPlugin {
    public static final String KERBEROS = "GSSAPI";
    private static final Logger LOG = LoggerFactory.getLogger(KerberosSaslTransportPlugin.class);

    public TTransportFactory getServerTransportFactory() throws IOException {
        // create an authentication callback handler
        CallbackHandler server_callback_handler = new ServerCallbackHandler(login_conf, storm_conf);

        // login our principal
        Subject subject = null;
        try {
            // specify a configuration object to be used
            Configuration.setConfiguration(login_conf);
            // now login
            Login login = new Login(AuthUtils.LOGIN_CONTEXT_SERVER, server_callback_handler);
            subject = login.getSubject();
        } catch (LoginException ex) {
            LOG.error("Server failed to login in principal:" + ex, ex);
            throw new RuntimeException(ex);
        }

        // check the credential of our principal
        if (subject.getPrivateCredentials(KerberosTicket.class).isEmpty()) {
            throw new RuntimeException("Fail to verify user principal with section \""
                    + AuthUtils.LOGIN_CONTEXT_SERVER + "\" in login configuration file " + login_conf);
        }

        String principal = AuthUtils.get(login_conf, AuthUtils.LOGIN_CONTEXT_SERVER, "principal");
        LOG.debug("principal:" + principal);
        KerberosName serviceKerberosName = new KerberosName(principal);
        String serviceName = serviceKerberosName.getServiceName();
        String hostName = serviceKerberosName.getHostName();
        Map<String, String> props = new TreeMap<String, String>();
        props.put(Sasl.QOP, "auth");
        props.put(Sasl.SERVER_AUTH, "false");

        // create a transport factory that will invoke our auth callback for digest
        TSaslServerTransport.Factory factory = new TSaslServerTransport.Factory();
        factory.addServerDefinition(KERBEROS, serviceName, hostName, props, server_callback_handler);

        // create a wrap transport factory so that we could apply user credential during connections
        TUGIAssumingTransportFactory wrapFactory = new TUGIAssumingTransportFactory(factory, subject);

        LOG.info("SASL GSSAPI transport factory will be used");
        return wrapFactory;
    }

    @Override
    public TTransport connect(TTransport transport, String serverHost, String asUser)
            throws TTransportException, IOException {
        // create an authentication callback handler
        ClientCallbackHandler client_callback_handler = new ClientCallbackHandler(login_conf);

        // login our user
        Login login = null;
        try {
            // specify a configuration object to be used
            Configuration.setConfiguration(login_conf);
            // now login
            login = new Login(AuthUtils.LOGIN_CONTEXT_CLIENT, client_callback_handler);
        } catch (LoginException ex) {
            LOG.error("Server failed to login in principal:" + ex, ex);
            throw new RuntimeException(ex);
        }

        final Subject subject = login.getSubject();
        if (subject.getPrivateCredentials(KerberosTicket.class).isEmpty()) { // error
            throw new RuntimeException("Fail to verify user principal with section \""
                    + AuthUtils.LOGIN_CONTEXT_CLIENT + "\" in login configuration file " + login_conf);
        }

        final String principal = StringUtils.isBlank(asUser) ? getPrincipal(subject) : asUser;
        String serviceName = AuthUtils.get(login_conf, AuthUtils.LOGIN_CONTEXT_CLIENT, "serviceName");
        if (serviceName == null) {
            serviceName = AuthUtils.SERVICE;
        }
        Map<String, String> props = new TreeMap<String, String>();
        props.put(Sasl.QOP, "auth");
        props.put(Sasl.SERVER_AUTH, "false");

        LOG.debug("SASL GSSAPI client transport is being established");
        final TTransport sasalTransport = new TSaslClientTransport(KERBEROS, principal, serviceName, serverHost,
                props, null, transport);

        // open Sasl transport with the login credential
        try {
            Subject.doAs(subject, new PrivilegedExceptionAction<Void>() {
                public Void run() {
                    try {
                        LOG.debug("do as:" + principal);
                        sasalTransport.open();
                    } catch (Exception e) {
                        LOG.error(
                                "Client failed to open SaslClientTransport to interact with a server during session initiation: "
                                        + e,
                                e);
                    }
                    return null;
                }
            });
        } catch (PrivilegedActionException e) {
            throw new RuntimeException(e);
        }

        return sasalTransport;
    }

    private String getPrincipal(Subject subject) {
        Set<Principal> principals = (Set<Principal>) subject.getPrincipals();
        if (principals == null || principals.size() < 1) {
            LOG.info("No principal found in login subject");
            return null;
        }
        return ((Principal) (principals.toArray()[0])).getName();
    }

    /**
     * A TransportFactory that wraps another one, but assumes a specified UGI before calling through.
     * 
     * This is used on the server side to assume the server's Principal when accepting clients.
     */
    static class TUGIAssumingTransportFactory extends TTransportFactory {
        private final Subject subject;
        private final TTransportFactory wrapped;

        public TUGIAssumingTransportFactory(TTransportFactory wrapped, Subject subject) {
            this.wrapped = wrapped;
            this.subject = subject;

            Set<Principal> principals = (Set<Principal>) subject.getPrincipals();
            if (principals.size() > 0)
                LOG.info("Service principal:" + ((Principal) (principals.toArray()[0])).getName());
        }

        @Override
        public TTransport getTransport(final TTransport trans) {
            try {
                return Subject.doAs(subject, new PrivilegedExceptionAction<TTransport>() {
                    public TTransport run() {
                        try {
                            return wrapped.getTransport(trans);
                        } catch (Exception e) {
                            LOG.error(
                                    "Storm server failed to open transport to interact with a client during session initiation: "
                                            + e,
                                    e);
                            return null;
                        }
                    }
                });
            } catch (PrivilegedActionException e) {
                LOG.error(
                        "Storm server experienced a PrivilegedActionException exception while creating a transport using a JAAS principal context:"
                                + e,
                        e);
                return null;
            }
        }
    }
}