blob: da4006a6e514f31941ca86e56ea62d1f07c3feff [file] [log] [blame]
/*
* 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.ranger.authorization.kms.authorizer;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.crypto.key.kms.server.KMSACLsType;
import org.apache.hadoop.crypto.key.kms.server.KMSConfiguration;
import org.apache.hadoop.crypto.key.kms.server.KMSWebApp;
import org.apache.hadoop.crypto.key.kms.server.KMS.KMSOp;
import org.apache.hadoop.crypto.key.kms.server.KMSACLsType.Type;
import org.apache.hadoop.crypto.key.kms.server.KeyAuthorizationKeyProvider.KeyACLs;
import org.apache.hadoop.crypto.key.kms.server.KeyAuthorizationKeyProvider.KeyOpType;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.SecureClientLogin;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.authorize.AccessControlList;
import org.apache.hadoop.security.authorize.AuthorizationException;
import org.apache.ranger.audit.provider.MiscUtil;
import org.apache.ranger.plugin.audit.RangerDefaultAuditHandler;
import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl;
import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl;
import org.apache.ranger.plugin.policyengine.RangerAccessResult;
import org.apache.ranger.plugin.service.RangerBasePlugin;
import org.apache.ranger.plugin.util.RangerPerfTracer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Sets;
public class RangerKmsAuthorizer implements Runnable, KeyACLs {
private static final Logger LOG = LoggerFactory.getLogger(RangerKmsAuthorizer.class);
private static final Log PERF_KMSAUTH_REQUEST_LOG = RangerPerfTracer.getPerfLogger("kmsauth.request");
private static final String KMS_USER_PRINCIPAL = "ranger.ks.kerberos.principal";
private static final String KMS_USER_KEYTAB = "ranger.ks.kerberos.keytab";
private static final String KMS_NAME_RULES = "hadoop.security.auth_to_local";
private static final String UNAUTHORIZED_MSG_WITH_KEY =
"User:%s not allowed to do '%s' on '%s'";
private static final String UNAUTHORIZED_MSG_WITHOUT_KEY =
"User:%s not allowed to do '%s'";
public static final int RELOADER_SLEEP_MILLIS = 1000;
private static final Map<KMSACLsType.Type, String> ACCESS_TYPE_MAP = new HashMap<>();
private volatile Map<Type, AccessControlList> blacklistedAcls;
private long lastReload;
private ScheduledExecutorService executorService;
public static final String ACCESS_TYPE_DECRYPT_EEK = "decrypteek";
public static final String ACCESS_TYPE_GENERATE_EEK = "generateeek";
public static final String ACCESS_TYPE_GET_METADATA = "getmetadata";
public static final String ACCESS_TYPE_GET_KEYS = "getkeys";
public static final String ACCESS_TYPE_GET = "get";
public static final String ACCESS_TYPE_SET_KEY_MATERIAL= "setkeymaterial";
public static final String ACCESS_TYPE_ROLLOVER = "rollover";
public static final String ACCESS_TYPE_CREATE = "create";
public static final String ACCESS_TYPE_DELETE = "delete";
private static volatile RangerKMSPlugin kmsPlugin = null;
/**
* Constant that identifies the authentication mechanism.
*/
public static final String TYPE = "kerberos";
/**
* Constant for the configuration property that indicates the kerberos principal.
*/
public static final String PRINCIPAL = TYPE + ".principal";
/**
* Constant for the configuration property that indicates the keytab file path.
*/
public static final String KEYTAB = TYPE + ".keytab";
static {
ACCESS_TYPE_MAP.put(KMSACLsType.Type.CREATE, RangerKmsAuthorizer.ACCESS_TYPE_CREATE);
ACCESS_TYPE_MAP.put(KMSACLsType.Type.DELETE, RangerKmsAuthorizer.ACCESS_TYPE_DELETE);
ACCESS_TYPE_MAP.put(KMSACLsType.Type.ROLLOVER, RangerKmsAuthorizer.ACCESS_TYPE_ROLLOVER);
ACCESS_TYPE_MAP.put(KMSACLsType.Type.GET, RangerKmsAuthorizer.ACCESS_TYPE_GET);
ACCESS_TYPE_MAP.put(KMSACLsType.Type.GET_KEYS, RangerKmsAuthorizer.ACCESS_TYPE_GET_KEYS);
ACCESS_TYPE_MAP.put(KMSACLsType.Type.GET_METADATA, RangerKmsAuthorizer.ACCESS_TYPE_GET_METADATA);
ACCESS_TYPE_MAP.put(KMSACLsType.Type.SET_KEY_MATERIAL, RangerKmsAuthorizer.ACCESS_TYPE_SET_KEY_MATERIAL);
ACCESS_TYPE_MAP.put(KMSACLsType.Type.GENERATE_EEK, RangerKmsAuthorizer.ACCESS_TYPE_GENERATE_EEK);
ACCESS_TYPE_MAP.put(KMSACLsType.Type.DECRYPT_EEK, RangerKmsAuthorizer.ACCESS_TYPE_DECRYPT_EEK);
}
RangerKmsAuthorizer(Configuration conf) {
LOG.info("RangerKmsAuthorizer(conf)...");
if (conf == null) {
conf = loadACLs();
}
authWithKerberos(conf);
setKMSACLs(conf);
init(conf);
}
private void authWithKerberos(Configuration conf) {
String localHostName = null;
try {
localHostName = java.net.InetAddress.getLocalHost().getCanonicalHostName();
} catch (UnknownHostException e1) {
LOG.warn("Error getting local host name : "+e1.getMessage());
}
String principal = null;
try {
principal = SecureClientLogin.getPrincipal(conf.get(KMS_USER_PRINCIPAL), localHostName);
} catch (IOException e1) {
LOG.warn("Error getting "+KMS_USER_PRINCIPAL+" : "+e1.getMessage());
}
String keytab = conf.get(KMS_USER_KEYTAB);
String nameRules = conf.get(KMS_NAME_RULES);
if(LOG.isDebugEnabled()){
LOG.debug("Ranger KMS Principal : "+principal+", Keytab : "+keytab+", NameRule : "+nameRules);
}
MiscUtil.authWithKerberos(keytab, principal, nameRules);
}
public RangerKmsAuthorizer() {
this(null);
}
@Override
public void run() {
try {
if (KMSConfiguration.isACLsFileNewer(lastReload)) {
setKMSACLs(loadACLs());
}
} catch (Exception ex) {
LOG.warn(
String.format("Could not reload ACLs file: '%s'", ex.toString()), ex);
}
}
private Configuration loadACLs() {
LOG.debug("Loading ACLs file");
lastReload = System.currentTimeMillis();
Configuration conf = KMSConfiguration.getACLsConf();
// triggering the resource loading.
conf.get(Type.CREATE.getAclConfigKey());
return conf;
}
@Override
public synchronized void startReloader() {
if (executorService == null) {
executorService = Executors.newScheduledThreadPool(1);
executorService.scheduleAtFixedRate(this, RELOADER_SLEEP_MILLIS,
RELOADER_SLEEP_MILLIS, TimeUnit.MILLISECONDS);
}
}
@Override
public synchronized void stopReloader() {
if (executorService != null) {
executorService.shutdownNow();
executorService = null;
}
}
/**
* First Check if user is in ACL for the KMS operation, if yes, then
* return true if user is not present in any configured blacklist for
* the operation
* @param type KMS Operation
* @param ugi UserGroupInformation of user
* @return true is user has access
*/
@Override
public boolean hasAccess(Type type, UserGroupInformation ugi, String clientIp) {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerKmsAuthorizer.hasAccess(" + type + ", " + ugi + ")");
}
RangerPerfTracer perf = null;
if(RangerPerfTracer.isPerfTraceEnabled(PERF_KMSAUTH_REQUEST_LOG)) {
perf = RangerPerfTracer.getPerfTracer(PERF_KMSAUTH_REQUEST_LOG, "RangerKmsAuthorizer.hasAccess(type=" + type + ")");
}
boolean ret = false;
RangerKMSPlugin plugin = kmsPlugin;
String rangerAccessType = getRangerAccessType(type);
AccessControlList blacklist = blacklistedAcls.get(type);
ret = (blacklist == null) || !blacklist.isUserInList(ugi);
if(!ret){
LOG.debug("Operation "+rangerAccessType+" blocked in the blacklist for user "+ugi.getUserName());
}
if(plugin != null && ret) {
RangerKMSAccessRequest request = new RangerKMSAccessRequest("", rangerAccessType, ugi, clientIp);
RangerAccessResult result = plugin.isAccessAllowed(request);
ret = result != null && result.getIsAllowed();
}
RangerPerfTracer.log(perf);
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerkmsAuthorizer.hasAccess(" + type + ", " + ugi + "): " + ret);
}
return ret;
}
public boolean hasAccess(Type type, UserGroupInformation ugi, String keyName, String clientIp) {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerKmsAuthorizer.hasAccess(" + type + ", " + ugi + " , "+keyName+")");
}
boolean ret = false;
RangerKMSPlugin plugin = kmsPlugin;
String rangerAccessType = getRangerAccessType(type);
AccessControlList blacklist = blacklistedAcls.get(type);
ret = (blacklist == null) || !blacklist.isUserInList(ugi);
if(!ret){
LOG.debug("Operation "+rangerAccessType+" blocked in the blacklist for user "+ugi.getUserName());
}
if(plugin != null && ret) {
RangerKMSAccessRequest request = new RangerKMSAccessRequest(keyName, rangerAccessType, ugi, clientIp);
RangerAccessResult result = plugin.isAccessAllowed(request);
ret = result != null && result.getIsAllowed();
}
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerkmsAuthorizer.hasAccess(" + type + ", " + ugi + " , "+keyName+ "): " + ret);
}
return ret;
}
@Override
public void assertAccess(Type aclType, UserGroupInformation ugi, KMSOp operation, String key, String clientIp)
throws AccessControlException {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerKmsAuthorizer.assertAccess(" + key + ", " + ugi +", " + aclType + ")");
}
key = (key == null)?"":key;
if (!hasAccess(aclType, ugi, key, clientIp)) {
KMSWebApp.getUnauthorizedCallsMeter().mark();
KMSWebApp.getKMSAudit().unauthorized(ugi, operation, key);
throw new AuthorizationException(String.format(
(!key.equals("")) ? UNAUTHORIZED_MSG_WITH_KEY
: UNAUTHORIZED_MSG_WITHOUT_KEY,
ugi.getShortUserName(), operation, key));
}
}
@Override
public boolean hasAccessToKey(String keyName, UserGroupInformation ugi, KeyOpType opType) {
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerKmsAuthorizer.hasAccessToKey(" + keyName + ", " + ugi +", " + opType + ")");
}
return true;
}
@Override
public boolean isACLPresent(String keyName, KeyOpType opType) {
return true;
}
public void init(Configuration conf) {
if(LOG.isDebugEnabled()) {
LOG.debug("==> RangerKmsAuthorizer.init()");
}
RangerKMSPlugin plugin = kmsPlugin;
if(plugin == null) {
synchronized(RangerKmsAuthorizer.class) {
plugin = kmsPlugin;
if(plugin == null) {
plugin = new RangerKMSPlugin();
plugin.init();
kmsPlugin = plugin;
}
}
}
if(LOG.isDebugEnabled()) {
LOG.debug("<== RangerkmsAuthorizer.init()");
}
}
private void setKMSACLs(Configuration conf) {
Map<Type, AccessControlList> tempBlacklist = new HashMap<Type, AccessControlList>();
for (Type aclType : Type.values()) {
String blacklistStr = conf.get(aclType.getBlacklistConfigKey());
if (blacklistStr != null) {
// Only add if blacklist is present
tempBlacklist.put(aclType, new AccessControlList(blacklistStr));
LOG.info("'{}' Blacklist '{}'", aclType, blacklistStr);
}
}
blacklistedAcls = tempBlacklist;
}
private static String getRangerAccessType(KMSACLsType.Type accessType) {
if (ACCESS_TYPE_MAP.containsKey(accessType)) {
return ACCESS_TYPE_MAP.get(accessType);
}
return null;
}
}
class RangerKMSPlugin extends RangerBasePlugin {
public RangerKMSPlugin() {
super("kms", "kms");
}
@Override
public void init() {
super.init();
RangerDefaultAuditHandler auditHandler = new RangerDefaultAuditHandler(getConfig());
super.setResultProcessor(auditHandler);
}
}
class RangerKMSResource extends RangerAccessResourceImpl {
private static final String KEY_NAME = "keyname";
public RangerKMSResource(String keyname) {
setValue(KEY_NAME, keyname != null ? keyname : null);
}
}
class RangerKMSAccessRequest extends RangerAccessRequestImpl {
public RangerKMSAccessRequest(String keyName, String accessType, UserGroupInformation ugi, String clientIp) {
super.setResource(new RangerKMSResource(keyName));
super.setAccessType(accessType);
super.setUser(ugi.getShortUserName());
super.setUserGroups(Sets.newHashSet(ugi.getGroupNames()));
super.setAccessTime(new Date());
super.setClientIPAddress(clientIp);
super.setAction(accessType);
}
}