blob: e65577c98d76abf0887a5bc14669c4b58f1c1981 [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.hadoop.crypto.key.kms.server;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.conf.Configuration;
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.UserGroupInformation;
import org.apache.hadoop.security.authorize.AccessControlList;
import org.apache.hadoop.security.authorize.AuthorizationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.google.common.annotations.VisibleForTesting;
/**
* Provides access to the <code>AccessControlList</code>s used by KMS,
* hot-reloading them if the <code>dbks-site.xml</code> file where the ACLs
* are defined has been updated.
*/
@InterfaceAudience.Private
public class KMSACLs implements Runnable, KeyACLs {
private static final Logger LOG = LoggerFactory.getLogger(KMSACLs.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 String ACL_DEFAULT = AccessControlList.WILDCARD_ACL_VALUE;
public static final int RELOADER_SLEEP_MILLIS = 1000;
private volatile Map<Type, AccessControlList> acls;
private volatile Map<Type, AccessControlList> blacklistedAcls;
@VisibleForTesting
volatile Map<String, HashMap<KeyOpType, AccessControlList>> keyAcls;
@VisibleForTesting
volatile Map<KeyOpType, AccessControlList> defaultKeyAcls = new HashMap<>();
@VisibleForTesting
volatile Map<KeyOpType, AccessControlList> whitelistKeyAcls = new HashMap<>();
private ScheduledExecutorService executorService;
private long lastReload;
KMSACLs(Configuration conf) {
if (conf == null) {
conf = loadACLs();
}
setKMSACLs(conf);
setKeyACLs(conf);
}
public KMSACLs() {
this(null);
}
private void setKMSACLs(Configuration conf) {
Map<Type, AccessControlList> tempAcls = new HashMap<Type, AccessControlList>();
Map<Type, AccessControlList> tempBlacklist = new HashMap<Type, AccessControlList>();
for (Type aclType : Type.values()) {
String aclStr = conf.get(aclType.getAclConfigKey(), ACL_DEFAULT);
tempAcls.put(aclType, new AccessControlList(aclStr));
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);
}
LOG.info("'{}' ACL '{}'", aclType, aclStr);
}
acls = tempAcls;
blacklistedAcls = tempBlacklist;
}
@VisibleForTesting
void setKeyACLs(Configuration conf) {
Map<String, HashMap<KeyOpType, AccessControlList>> tempKeyAcls = new HashMap<String, HashMap<KeyOpType,AccessControlList>>();
Map<String, String> allKeyACLS = conf.getValByRegex(KMSConfiguration.KEY_ACL_PREFIX_REGEX);
for (Map.Entry<String, String> keyAcl : allKeyACLS.entrySet()) {
String k = keyAcl.getKey();
// this should be of type "key.acl.<KEY_NAME>.<OP_TYPE>"
int keyNameStarts = KMSConfiguration.KEY_ACL_PREFIX.length();
int keyNameEnds = k.lastIndexOf(".");
if (keyNameStarts >= keyNameEnds) {
LOG.warn("Invalid key name '{}'", k);
} else {
String aclStr = keyAcl.getValue();
String keyName = k.substring(keyNameStarts, keyNameEnds);
String keyOp = k.substring(keyNameEnds + 1);
KeyOpType aclType = null;
try {
aclType = KeyOpType.valueOf(keyOp);
} catch (IllegalArgumentException e) {
LOG.warn("Invalid key Operation '{}'", keyOp);
}
if (aclType != null) {
// On the assumption this will be single threaded.. else we need to
// ConcurrentHashMap
HashMap<KeyOpType,AccessControlList> aclMap =
tempKeyAcls.get(keyName);
if (aclMap == null) {
aclMap = new HashMap<KeyOpType, AccessControlList>();
tempKeyAcls.put(keyName, aclMap);
}
aclMap.put(aclType, new AccessControlList(aclStr));
LOG.info("KEY_NAME '{}' KEY_OP '{}' ACL '{}'",
keyName, aclType, aclStr);
}
}
}
keyAcls = tempKeyAcls;
final Map<KeyOpType, AccessControlList> tempDefaults = new HashMap<>();
final Map<KeyOpType, AccessControlList> tempWhitelists = new HashMap<>();
for (KeyOpType keyOp : KeyOpType.values()) {
parseAclsWithPrefix(conf, KMSConfiguration.DEFAULT_KEY_ACL_PREFIX,keyOp, tempDefaults);
parseAclsWithPrefix(conf, KMSConfiguration.WHITELIST_KEY_ACL_PREFIX,keyOp, tempWhitelists);
}
defaultKeyAcls = tempDefaults;
whitelistKeyAcls = tempWhitelists;
}
/**
* Parse the acls from configuration with the specified prefix. Currently
* only 2 possible prefixes: whitelist and default.
*
* @param conf The configuration.
* @param prefix The prefix.
* @param keyOp The key operation.
* @param results The collection of results to add to.
*/
private void parseAclsWithPrefix(final Configuration conf,final String prefix, final KeyOpType keyOp,Map<KeyOpType, AccessControlList> results) {
String confKey = prefix + keyOp;
String aclStr = conf.get(confKey);
if (aclStr != null) {
if (keyOp == KeyOpType.ALL) {
// Ignore All operation for default key and whitelist key acls
LOG.warn("Invalid KEY_OP '{}' for {}, ignoring", keyOp, prefix);
} else {
if (aclStr.equals("*")) {
LOG.info("{} for KEY_OP '{}' is set to '*'", prefix, keyOp);
}
results.put(keyOp, new AccessControlList(aclStr));
}
}
}
@Override
public void run() {
try {
if (KMSConfiguration.isACLsFileNewer(lastReload)) {
setKMSACLs(loadACLs());
setKeyACLs(loadACLs());
}
} catch (Exception ex) {
LOG.warn(
String.format("Could not reload ACLs file: '%s'", ex.toString()), ex);
}
}
@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;
}
}
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;
}
/**
* 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) {
boolean access = acls.get(type).isUserAllowed(ugi);
if (LOG.isDebugEnabled()) {
LOG.debug("Checking user [{}] for: {} {} ", ugi.getShortUserName(),
type.toString(), acls.get(type).getAclString());
}
if (access) {
AccessControlList blacklist = blacklistedAcls.get(type);
access = (blacklist == null) || !blacklist.isUserInList(ugi);
if (LOG.isDebugEnabled()) {
if (blacklist == null) {
LOG.debug("No blacklist for {}", type.toString());
} else if (access) {
LOG.debug("user is not in {}" , blacklist.getAclString());
} else {
LOG.debug("user is in {}" , blacklist.getAclString());
}
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("User: [{}], Type: {} Result: {}", ugi.getShortUserName(),
type.toString(), access);
}
return access;
}
@Override
public void assertAccess(Type aclType,
UserGroupInformation ugi, KMSOp operation, String key, String clientIp)
throws AccessControlException {
if (!KMSWebApp.getACLs().hasAccess(aclType, ugi, clientIp)) {
KMSWebApp.getUnauthorizedCallsMeter().mark();
KMSWebApp.getKMSAudit().unauthorized(ugi, operation, key);
throw new AuthorizationException(String.format(
(key != null) ? UNAUTHORIZED_MSG_WITH_KEY
: UNAUTHORIZED_MSG_WITHOUT_KEY,
ugi.getShortUserName(), operation, key));
}
}
@Override
public boolean hasAccessToKey(String keyName, UserGroupInformation ugi,
KeyOpType opType) {
boolean access = checkKeyAccess(keyName, ugi, opType)
|| checkKeyAccess(whitelistKeyAcls, ugi, opType);
if (!access) {
KMSWebApp.getKMSAudit().unauthorized(ugi, opType, keyName);
}
return access;
}
private boolean checkKeyAccess(String keyName, UserGroupInformation ugi,KeyOpType opType) {
Map<KeyOpType, AccessControlList> keyAcl = keyAcls.get(keyName);
if (keyAcl == null) {
// If No key acl defined for this key, check to see if
// there are key defaults configured for this operation
LOG.debug("Key: {} has no ACLs defined, using defaults.", keyName);
keyAcl = defaultKeyAcls;
}
boolean access = checkKeyAccess(keyAcl, ugi, opType);
if (LOG.isDebugEnabled()) {
LOG.debug("User: [{}], OpType: {}, KeyName: {} Result: {}",
ugi.getShortUserName(), opType.toString(), keyName, access);
}
return access;
}
private boolean checkKeyAccess(Map<KeyOpType, AccessControlList> keyAcl,UserGroupInformation ugi, KeyOpType opType) {
AccessControlList acl = keyAcl.get(opType);
if (acl == null) {
// If no acl is specified for this operation,
// deny access
LOG.debug("No ACL available for key, denying access for {}", opType);
return false;
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Checking user [{}] for: {}: {}" + ugi.getShortUserName(),
opType.toString(), acl.getAclString());
}
return acl.isUserAllowed(ugi);
}
}
@Override
public boolean isACLPresent(String keyName, KeyOpType opType) {
return (keyAcls.containsKey(keyName) || defaultKeyAcls.containsKey(opType));
}
}