/*
* 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 org.apache.hadoop.hive.llap.daemon.impl;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.List;
import java.io.IOException;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.hive.llap.security.LlapTokenIdentifier;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.token.TokenIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class LlapTokenChecker {
private static final Logger LOG = LoggerFactory.getLogger(LlapTokenChecker.class);
public static final class LlapTokenInfo {
public final String userName;
public final String appId;
public final boolean isSigningRequired;
public LlapTokenInfo(String userName, String appId, boolean isSigningRequired) {
this.userName = userName;
this.appId = appId;
this.isSigningRequired = isSigningRequired;
}
}
private static final LlapTokenInfo NO_SECURITY = new LlapTokenInfo(null, null, false);
public static LlapTokenInfo getTokenInfo(String clusterId) throws IOException {
if (!UserGroupInformation.isSecurityEnabled()) return NO_SECURITY;
UserGroupInformation current = UserGroupInformation.getCurrentUser();
String kerberosName = current.hasKerberosCredentials() ? current.getShortUserName() : null;
List<LlapTokenIdentifier> tokens = getLlapTokens(current, clusterId);
if ((tokens == null || tokens.isEmpty()) && kerberosName == null) {
throw new SecurityException("No tokens or kerberos for " + current);
}
warnMultipleTokens(tokens);
return getTokenInfoInternal(kerberosName, tokens);
}
public static void warnMultipleTokens(List<LlapTokenIdentifier> tokens) {
if (tokens != null && tokens.size() > 1) {
StringBuilder sb = new StringBuilder("Found multiple LLAP tokens: [");
boolean isFirst = true;
for (LlapTokenIdentifier ti : tokens) {
if (!isFirst) {
sb.append(", ");
}
isFirst = false;
sb.append(ti);
}
LOG.warn(sb.append("]").toString());
}
}
static List<LlapTokenIdentifier> getLlapTokens(
UserGroupInformation ugi, String clusterId) {
List<LlapTokenIdentifier> tokens = null;
for (TokenIdentifier id : ugi.getTokenIdentifiers()) {
if (!LlapTokenIdentifier.KIND_NAME.equals(id.getKind())) continue;
if (LOG.isDebugEnabled()) {
LOG.debug("Token {}", id);
}
LlapTokenIdentifier llapId = (LlapTokenIdentifier)id;
if (clusterId != null && !clusterId.equals(llapId.getClusterId())) continue;
if (tokens == null) {
tokens = new ArrayList<>();
}
tokens.add((LlapTokenIdentifier)id);
}
return tokens;
}
@VisibleForTesting
static LlapTokenInfo getTokenInfoInternal(
String kerberosName, List<LlapTokenIdentifier> tokens) {
assert (tokens != null && !tokens.isEmpty()) || kerberosName != null;
if (tokens == null) {
return new LlapTokenInfo(kerberosName, null, true);
}
String userName = kerberosName, appId = null;
boolean isSigningRequired = false;
for (LlapTokenIdentifier llapId : tokens) {
String newUserName = llapId.getOwner().toString();
if (userName != null && !userName.equals(newUserName)) {
throw new SecurityException("Ambiguous user name from credentials - " + userName
+ " and " + newUserName + " from " + llapId
+ ((kerberosName == null) ? ("; has kerberos credentials for " + kerberosName) : ""));
}
userName = newUserName;
String newAppId = llapId.getAppId();
if (!StringUtils.isEmpty(newAppId)) {
if (!StringUtils.isEmpty(appId) && !appId.equals(newAppId)) {
throw new SecurityException("Ambiguous app ID from credentials - " + appId
+ " and " + newAppId + " from " + llapId);
}
appId = newAppId;
}
isSigningRequired = isSigningRequired || llapId.isSigningRequired();
}
assert userName != null;
return new LlapTokenInfo(userName, appId, isSigningRequired);
}
public static void checkPermissions(
String clusterId, String userName, String appId, Object hint) throws IOException {
if (!UserGroupInformation.isSecurityEnabled()) return;
Preconditions.checkNotNull(userName);
UserGroupInformation current = UserGroupInformation.getCurrentUser();
String kerberosName = current.hasKerberosCredentials() ? current.getShortUserName() : null;
List<LlapTokenIdentifier> tokens = getLlapTokens(current, clusterId);
checkPermissionsInternal(kerberosName, tokens, userName, appId, hint);
}
@VisibleForTesting
static void checkPermissionsInternal(String kerberosName, List<LlapTokenIdentifier> tokens,
String userName, String appId, Object hint) {
if (appId == null) {
appId = "";
}
if (kerberosName != null && StringUtils.isBlank(appId) && kerberosName.equals(userName)) {
return;
}
if (tokens != null) {
for (LlapTokenIdentifier llapId : tokens) {
String tokenUser = llapId.getOwner().toString(), tokenAppId = llapId.getAppId();
if (checkTokenPermissions(userName, appId, tokenUser, tokenAppId)) return;
}
}
throw new SecurityException(
"Unauthorized to access " + userName + ", " + appId + " (" + hint + ")");
}
public static void checkPermissions(
LlapTokenInfo prm, String userName, String appId, Object hint) {
if (userName == null) {
assert StringUtils.isEmpty(appId);
return;
}
if (!checkTokenPermissions(userName, appId, prm.userName, prm.appId)) {
throw new SecurityException("Unauthorized to access "
+ userName + ", " + appId + " (" + hint + ")");
}
}
private static boolean checkTokenPermissions(
String userName, String appId, String tokenUser, String tokenAppId) {
return userName.equals(tokenUser)
&& (StringUtils.isBlank(appId) || appId.equals(tokenAppId));
}
}