/* * Copyright © 2015-2016 Cask Data, Inc. * * 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 co.cask.cdap.security; import co.cask.cdap.common.conf.CConfiguration; import co.cask.cdap.common.conf.Constants; import co.cask.cdap.common.security.YarnTokenUtils; import co.cask.cdap.data.security.HBaseTokenUtils; import co.cask.cdap.hive.ExploreUtils; import co.cask.cdap.security.hive.HiveTokenUtils; import co.cask.cdap.security.hive.JobHistoryServerTokenUtils; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.inject.Inject; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hdfs.DFSConfigKeys; import org.apache.hadoop.hive.thrift.HadoopThriftAuthBridge; import org.apache.hadoop.mapreduce.MRConfig; import org.apache.hadoop.security.Credentials; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.twill.api.RunId; import org.apache.twill.api.SecureStore; import org.apache.twill.api.SecureStoreUpdater; import org.apache.twill.filesystem.FileContextLocationFactory; import org.apache.twill.filesystem.ForwardingLocationFactory; import org.apache.twill.filesystem.HDFSLocationFactory; import org.apache.twill.filesystem.LocationFactory; import org.apache.twill.internal.yarn.YarnUtils; import org.apache.twill.yarn.YarnSecureStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; /** * A {@link SecureStoreUpdater} that updates all secure tokens used by the platform. */ public final class TokenSecureStoreUpdater implements SecureStoreUpdater { private static final Logger LOG = LoggerFactory.getLogger(TokenSecureStoreUpdater.class); private final YarnConfiguration hConf; private final LocationFactory locationFactory; private final long updateInterval; private final boolean secureExplore; @Inject public TokenSecureStoreUpdater(YarnConfiguration hConf, CConfiguration cConf, LocationFactory locationFactory) { this.hConf = hConf; this.locationFactory = locationFactory; secureExplore = cConf.getBoolean(Constants.Explore.EXPLORE_ENABLED) && UserGroupInformation.isSecurityEnabled(); updateInterval = calculateUpdateInterval(); } private Credentials refreshCredentials() { try { Credentials refreshedCredentials = new Credentials(); if (User.isSecurityEnabled()) { YarnTokenUtils.obtainToken(hConf, refreshedCredentials); } if (User.isHBaseSecurityEnabled(hConf)) { HBaseTokenUtils.obtainToken(hConf, refreshedCredentials); } if (secureExplore) { HiveTokenUtils.obtainToken(refreshedCredentials); JobHistoryServerTokenUtils.obtainToken(hConf, refreshedCredentials); } addDelegationTokens(hConf, locationFactory, refreshedCredentials); return refreshedCredentials; } catch (IOException ioe) { throw Throwables.propagate(ioe); } } /** * Helper method to get delegation tokens for the given LocationFactory. * @param config The hadoop configuration. * @param locationFactory The LocationFactory for generating tokens. * @param credentials Credentials for storing tokens acquired. * @return List of delegation Tokens acquired. * TODO: copied from Twill 0.6 YarnUtils for CDAP-5350. Remove after this fix is moved to Twill. */ private static List<Token<?>> addDelegationTokens(Configuration config, LocationFactory locationFactory, Credentials credentials) throws IOException { if (!UserGroupInformation.isSecurityEnabled()) { LOG.debug("Security is not enabled"); return ImmutableList.of(); } FileSystem fileSystem = getFileSystem(locationFactory, config); if (fileSystem == null) { LOG.warn("Unexpected: LocationFactory is not HDFS. Not getting delegation tokens."); return ImmutableList.of(); } String renewer = YarnUtils.getYarnTokenRenewer(config); Token<?>[] tokens = fileSystem.addDelegationTokens(renewer, credentials); LOG.info("Added HDFS DelegationTokens: {}", Arrays.toString(tokens)); return tokens == null ? ImmutableList.<Token<?>>of() : ImmutableList.copyOf(tokens); } /** * Gets the Hadoop FileSystem from LocationFactory. * TODO: copied from Twill 0.6 YarnUtils for CDAP-5350. Remove after this fix is moved to Twill. */ private static FileSystem getFileSystem(LocationFactory locationFactory, Configuration config) throws IOException { LOG.debug("getFileSystem(): locationFactory is a {}", locationFactory.getClass()); if (locationFactory instanceof HDFSLocationFactory) { return ((HDFSLocationFactory) locationFactory).getFileSystem(); } if (locationFactory instanceof ForwardingLocationFactory) { return getFileSystem(((ForwardingLocationFactory) locationFactory).getDelegate(), config); } // CDAP-5350: For encrypted file systems, FileContext does not acquire the KMS delegation token // Since we know we are in Yarn, it is safe to get the FileSystem directly, bypassing LocationFactory. if (locationFactory instanceof FileContextLocationFactory) { return FileSystem.get(config); } return null; } /** * Since Hive classes are not in MasterServiceMain's classpath, create an instance of HiveConf using reflection. * Call this method only if explore is enabled. */ private Configuration getHiveConf() { ClassLoader hiveClassloader = ExploreUtils.getExploreClassloader(); ClassLoader contextClassloader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(hiveClassloader); try { Class<?> clz = hiveClassloader.loadClass("org.apache.hadoop.hive.conf.HiveConf"); return (Configuration) clz.newInstance(); } catch (Exception e) { LOG.error("Could not create an instance of HiveConf. Using default values.", e); return null; } finally { Thread.currentThread().setContextClassLoader(contextClassloader); } } private long calculateUpdateInterval() { List<Long> renewalTimes = Lists.newArrayList(); renewalTimes.add(hConf.getLong(DFSConfigKeys.DFS_NAMENODE_DELEGATION_TOKEN_RENEW_INTERVAL_KEY, DFSConfigKeys.DFS_NAMENODE_DELEGATION_TOKEN_RENEW_INTERVAL_DEFAULT)); // The value contains in hbase-default.xml, so it should always there. If it is really missing, default it to 1 day. renewalTimes.add(hConf.getLong(Constants.HBase.AUTH_KEY_UPDATE_INTERVAL, TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS))); if (secureExplore) { // Renewal interval for YARN renewalTimes.add(hConf.getLong(YarnConfiguration.DELEGATION_TOKEN_RENEW_INTERVAL_KEY, YarnConfiguration.DELEGATION_TOKEN_RENEW_INTERVAL_DEFAULT)); // Renewal interval for Hive. Also see: https://issues.apache.org/jira/browse/HIVE-9214 Configuration hiveConf = getHiveConf(); if (hiveConf != null) { renewalTimes.add(hiveConf.getLong(HadoopThriftAuthBridge.Server.DELEGATION_TOKEN_RENEW_INTERVAL_KEY, HadoopThriftAuthBridge.Server.DELEGATION_TOKEN_RENEW_INTERVAL_DEFAULT)); } else { renewalTimes.add(HadoopThriftAuthBridge.Server.DELEGATION_TOKEN_RENEW_INTERVAL_DEFAULT); } // Renewal interval for JHS renewalTimes.add(hConf.getLong(MRConfig.DELEGATION_TOKEN_RENEW_INTERVAL_KEY, MRConfig.DELEGATION_TOKEN_RENEW_INTERVAL_DEFAULT)); } // Set the update interval to the shortest update interval of all required renewals. Long minimumInterval = Collections.min(renewalTimes); // Schedule it 5 min before it expires long delay = minimumInterval - TimeUnit.MINUTES.toMillis(5); // Safeguard: In practice, the value can't be that small, otherwise nothing would work. if (delay <= 0) { delay = (minimumInterval <= 2) ? 1 : minimumInterval / 2; } LOG.info("Setting token renewal time to: {} ms", delay); return delay; } /** * Returns the minimum update interval for the delegation tokens. * @return The update interval in milliseconds. */ public long getUpdateInterval() { return updateInterval; } @Override public SecureStore update(String application, RunId runId) { Credentials credentials = refreshCredentials(); LOG.info("Updated credentials {}", credentials.getAllTokens()); return YarnSecureStore.create(credentials); } }