blob: 2324f8dece12e651fbcb85e8ca44bd94e2b13557 [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.net.InetAddress;
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 java.util.regex.Pattern;
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.ipc.Server;
import org.apache.hadoop.security.AccessControlException;
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.authorization.utils.StringUtil;
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.mortbay.log.Log;
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 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 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";
/**
* Constant for the configuration property that indicates the Kerberos name
* rules for the Kerberos principals.
*/
public static final String NAME_RULES = TYPE + ".name.rules";
RangerKmsAuthorizer(Configuration conf) {
LOG.info("RangerKmsAuthorizer(conf)...");
authWithKerberos();
if (conf == null) {
conf = loadACLs();
}
setKMSACLs(conf);
init(conf);
}
/**
*
*/
private void authWithKerberos() {
//Let's if we can create the login user UGI
Configuration kconf = new Configuration();
kconf.addResource("kms-site.xml");
String keytab = kconf.get("hadoop.kms.authentication.kerberos.keytab");
String principal = kconf.get("hadoop.kms.authentication.kerberos.principal");
String nameRules = kconf.get(NAME_RULES);
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 + ")");
}
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 ? false : result.getIsAllowed();
}
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 ? false : 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 + ")");
}
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) {
String ret = null;
switch(accessType) {
case CREATE:
ret = RangerKmsAuthorizer.ACCESS_TYPE_CREATE;
break;
case DELETE:
ret = RangerKmsAuthorizer.ACCESS_TYPE_DELETE;
break;
case ROLLOVER:
ret = RangerKmsAuthorizer.ACCESS_TYPE_ROLLOVER;
break;
case GET:
ret = RangerKmsAuthorizer.ACCESS_TYPE_GET;
break;
case GET_KEYS:
ret = RangerKmsAuthorizer.ACCESS_TYPE_GET_KEYS;
break;
case GET_METADATA:
ret = RangerKmsAuthorizer.ACCESS_TYPE_GET_METADATA;
break;
case SET_KEY_MATERIAL:
ret = RangerKmsAuthorizer.ACCESS_TYPE_SET_KEY_MATERIAL;
break;
case GENERATE_EEK:
ret = RangerKmsAuthorizer.ACCESS_TYPE_GENERATE_EEK;
break;
case DECRYPT_EEK:
ret = RangerKmsAuthorizer.ACCESS_TYPE_DECRYPT_EEK;
break;
}
return ret;
}
}
class RangerKMSPlugin extends RangerBasePlugin {
public RangerKMSPlugin() {
super("kms", "kms");
}
@Override
public void init() {
super.init();
RangerDefaultAuditHandler auditHandler = new RangerDefaultAuditHandler();
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);
}
}