/* * Copyright 2015 Kakao Corporation * * Licensed 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.kakao.hbase.common; import com.google.common.annotations.VisibleForTesting; import com.kakao.hbase.common.util.Util; import com.kakao.hbase.specific.CommandAdapter; import com.kakao.hbase.specific.HBaseAdminWrapper; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.MasterNotRunningException; import org.apache.hadoop.hbase.ZooKeeperConnectionException; import org.apache.hadoop.hbase.client.HBaseAdmin; import org.apache.hadoop.security.UserGroupInformation; import sun.security.krb5.Config; import javax.security.auth.callback.*; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import java.io.*; import java.util.Map; import java.util.Scanner; public class HBaseClient { private static String principal = null; private static String password = null; private static HBaseAdmin admin = null; private HBaseClient() { } private static String createJaasConfigFile(Args args) throws FileNotFoundException, UnsupportedEncodingException { // fixme hash collision may occur in args.hashStr() final String authConfFileName = "/tmp/" + "hbase-client-" + args.hashStr() + ".jaas"; File file = new File(authConfFileName); if (file.exists()) return authConfFileName; StringBuilder sb = new StringBuilder(); sb.append("Client {\n"); sb.append("com.sun.security.auth.module.Krb5LoginModule required\n"); sb.append("useTicketCache=false\n"); if (args.has(Args.OPTION_DEBUG)) { sb.append("debug=true\n"); } if (args.has(Args.OPTION_KEY_TAB, Args.OPTION_KEY_TAB_SHORT)) { sb.append("useKeyTab=true\n"); sb.append("keyTab=\"").append(args.valueOf(Args.OPTION_KEY_TAB, Args.OPTION_KEY_TAB_SHORT)).append("\"\n"); sb.append("principal=\"").append(principal(args)).append("\"\n"); } else { sb.append("useKeyTab=false\n"); } sb.append(";};"); try (PrintWriter writer = new PrintWriter(authConfFileName, Constant.CHARSET.name())) { writer.print(sb); } return authConfFileName; } private static String kerberosConfigFile(Args args) throws IOException { final String fileNameArg; if (args.has(Args.OPTION_KERBEROS_CONFIG)) { fileNameArg = (String) args.valueOf(Args.OPTION_KERBEROS_CONFIG); } else { fileNameArg = "/etc/krb5.conf"; } System.out.println("Loading kerberos config from " + fileNameArg); return fileNameArg; } private static void loginWithPassword(final Args args, Configuration conf) throws LoginException, IOException { LoginContext loginContext = new LoginContext("Client", new CallbackHandler() { @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback callback : callbacks) { if (callback instanceof NameCallback) { NameCallback nameCallback = (NameCallback) callback; nameCallback.setName(principal(args)); } else if (callback instanceof PasswordCallback) { PasswordCallback passwordCallback = (PasswordCallback) callback; passwordCallback.setPassword(askPassword().toCharArray()); } else { throw new UnsupportedCallbackException(callback); } } } }); loginContext.login(); CommandAdapter.loginUserFromSubject(conf, loginContext.getSubject()); } private static void updateConf(Configuration conf, String realm) throws IOException { conf.set("hadoop.security.authentication", "Kerberos"); conf.set("hbase.security.authentication", "Kerberos"); conf.set("hbase.master.kerberos.principal", "hbase/_HOST@" + realm); conf.set("hbase.regionserver.kerberos.principal", "hbase/_HOST@" + realm); } private static String principal(Args args) { if (principal == null) { if (args.has(Args.OPTION_PRINCIPAL, Args.OPTION_PRINCIPAL_SHORT)) { principal = (String) args.valueOf(Args.OPTION_PRINCIPAL, Args.OPTION_PRINCIPAL_SHORT); } else { System.out.print("Principal: "); Scanner scanner = new Scanner(System.in); principal = scanner.nextLine(); } } return principal; } private static String askPassword() { if (password == null) { Console console = System.console(); if (console == null) { System.out.print("Password: "); Scanner scanner = new Scanner(System.in); password = scanner.nextLine(); } else { password = String.valueOf(console.readPassword("Password: ")); } } return password; } private static boolean isSecuredCluster(Args args) { return args.has(Args.OPTION_KERBEROS_CONFIG) || args.has(Args.OPTION_PRINCIPAL) || args.has(Args.OPTION_PRINCIPAL_SHORT); } private static Configuration createBaseConfiguration(Args args) { Configuration confDefault = new Configuration(true); Configuration conf = HBaseConfiguration.create(confDefault); conf.set("hbase.zookeeper.quorum", args.getZookeeperQuorum()); conf.set("zookeeper.recovery.retry", "1"); conf.set("hbase.client.retries.number", "2"); conf.set("hbase.meta.scanner.caching", "1000"); for (Map.Entry<String, String> config : args.getConfigurations().entrySet()) { conf.set(config.getKey(), config.getValue()); } return conf; } private static void validateAuthentication() throws ZooKeeperConnectionException { try { // Is there something better? admin.isMasterRunning(); } catch (MasterNotRunningException e) { System.out.println("Maybe you are connecting to the secured cluster without kerberos config.\n"); } } private static synchronized void login(Args args, Configuration conf) throws Exception { if (args.has(Args.OPTION_DEBUG)) { System.setProperty("sun.security.krb5.debug", "true"); System.setProperty("sun.security.spnego.debug", "true"); } System.setProperty("java.security.auth.login.config", createJaasConfigFile(args)); System.setProperty("java.security.krb5.conf", kerberosConfigFile(args)); Config krbConfig = Config.getInstance(); final String realm; if (args.has(Args.OPTION_REALM)) { realm = (String) args.valueOf(Args.OPTION_REALM); System.setProperty("java.security.krb5.realm", realm); System.setProperty("java.security.krb5.kdc", krbConfig.getKDCList(realm)); Config.refresh(); } else { realm = krbConfig.getDefaultRealm(); } updateConf(conf, realm); if (args.has(Args.OPTION_KEY_TAB, Args.OPTION_KEY_TAB_SHORT)) { UserGroupInformation.setConfiguration(conf); UserGroupInformation.loginUserFromKeytab(principal(args), (String) args.valueOf(Args.OPTION_KEY_TAB, Args.OPTION_KEY_TAB_SHORT)); } else { loginWithPassword(args, conf); } UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); System.out.println(currentUser + "\n"); } public static HBaseAdmin getAdmin(Args args) throws Exception { System.out.println("Connecting to " + args.getZookeeperQuorum()); if (args.has(Args.OPTION_DEBUG)) Util.setLoggingThreshold("WARN"); if (admin == null) { Configuration conf = createBaseConfiguration(args); if (isSecuredCluster(args)) { login(args, conf); admin = new HBaseAdminWrapper(conf); } else { admin = new HBaseAdmin(conf); } } validateAuthentication(); return admin; } @VisibleForTesting public static void setAdminForTesting(HBaseAdmin admin) { HBaseClient.admin = admin; } }