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 org.apache.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.SortedMap; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.kerberos.KerberosTicket; import javax.security.auth.login.Configuration; import javax.security.auth.login.LoginException; import javax.security.sasl.Sasl; import org.apache.storm.messaging.netty.Login; 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.server.auth.KerberosName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.storm.security.auth.AuthUtils; import org.apache.storm.security.auth.SaslTransportPlugin; public class KerberosSaslTransportPlugin extends SaslTransportPlugin { public static final String KERBEROS = "GSSAPI"; private static final Logger LOG = LoggerFactory.getLogger(KerberosSaslTransportPlugin.class); private static Map<LoginCacheKey, Login> loginCache = new ConcurrentHashMap<>(); private class LoginCacheKey { private String _keyString = null; public LoginCacheKey(Configuration conf, String login_context) throws IOException { if (conf == null) { throw new IllegalArgumentException("Configuration should not be null"); } SortedMap<String, ?> authConf = AuthUtils.pullConfig(conf, login_context); if (authConf != null) { StringBuilder stringBuilder = new StringBuilder(); for (String configKey : authConf.keySet()) { String configValue = (String) authConf.get(configKey); stringBuilder.append(configKey); stringBuilder.append(configValue); } _keyString = stringBuilder.toString(); } else { throw new RuntimeException("Error in parsing the kerberos login Configuration, returned null"); } } @Override public int hashCode() { return _keyString.hashCode(); } @Override public boolean equals(Object obj) { return (obj instanceof LoginCacheKey) && _keyString.equals(((LoginCacheKey) obj)._keyString); } @Override public String toString() { return (_keyString); } } public TTransportFactory getServerTransportFactory() throws IOException { //create an authentication callback handler CallbackHandler server_callback_handler = new ServerCallbackHandler(login_conf, topoConf); //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(); login.startThreadIfNeeded(); } 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 LoginCacheKey key = new LoginCacheKey(login_conf, AuthUtils.LOGIN_CONTEXT_CLIENT); Login login = loginCache.get(key); if (login == null) { LOG.debug("Kerberos Login was not found in the Login Cache, attempting to contact the Kerberos Server"); synchronized (loginCache) { login = loginCache.get(key); if (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); login.startThreadIfNeeded(); loginCache.put(key, login); } 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.debug("Storm server failed to open transport " + "to interact with a client during session initiation: " + e, e); return new NoOpTTrasport(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; } } } }