HBASE-12745 Visibility Labels: support visibility labels for user groups. (Jerry He)

This commit is contained in:
anoopsjohn 2015-01-22 11:36:17 +05:30
parent 8b7a20f4ee
commit 833feefbf9
11 changed files with 668 additions and 83 deletions

View File

@ -641,6 +641,13 @@ public class AccessControlLists {
return aclKey.substring(GROUP_PREFIX.length());
}
/**
* Returns the group entry with the group prefix for a group principal.
*/
public static String toGroupEntry(String name) {
return GROUP_PREFIX + name;
}
public static boolean isNamespaceEntry(String entryName) {
return entryName.charAt(0) == NAMESPACE_PREFIX;
}

View File

@ -31,6 +31,7 @@ import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -63,8 +64,6 @@ import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
import com.google.common.collect.Lists;
@InterfaceAudience.Private
public class DefaultVisibilityLabelServiceImpl implements VisibilityLabelService {
@ -81,6 +80,7 @@ public class DefaultVisibilityLabelServiceImpl implements VisibilityLabelService
private VisibilityLabelsCache labelsCache;
private List<ScanLabelGenerator> scanLabelGenerators;
private List<String> superUsers;
private List<String> superGroups;
static {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@ -117,7 +117,10 @@ public class DefaultVisibilityLabelServiceImpl implements VisibilityLabelService
throw ioe;
}
this.scanLabelGenerators = VisibilityUtils.getScanLabelGenerators(this.conf);
this.superUsers = getSystemAndSuperUsers();
Pair<List<String>, List<String>> superUsersAndGroups =
VisibilityUtils.getSystemAndSuperUsers(this.conf);
this.superUsers = superUsersAndGroups.getFirst();
this.superGroups = superUsersAndGroups.getSecond();
if (e.getRegion().getRegionInfo().getTable().equals(LABELS_TABLE_NAME)) {
this.labelsRegion = e.getRegion();
Pair<Map<String, Integer>, Map<String, List<Integer>>> labelsAndUserAuths =
@ -203,21 +206,6 @@ public class DefaultVisibilityLabelServiceImpl implements VisibilityLabelService
}
}
protected List<String> getSystemAndSuperUsers() throws IOException {
User user = User.getCurrent();
if (user == null) {
throw new IOException("Unable to obtain the current user, "
+ "authorization checks for internal operations will not work correctly!");
}
if (LOG.isTraceEnabled()) {
LOG.trace("Current user name is " + user.getShortName());
}
String currentUser = user.getShortName();
List<String> superUsers = Lists.asList(currentUser,
this.conf.getStrings(AccessControlLists.SUPERUSER_CONF_KEY, new String[0]));
return superUsers;
}
@Override
public OperationStatus[] addLabels(List<byte[]> labels) throws IOException {
assert labelsRegion != null;
@ -276,7 +264,14 @@ public class DefaultVisibilityLabelServiceImpl implements VisibilityLabelService
public OperationStatus[] clearAuths(byte[] user, List<byte[]> authLabels) throws IOException {
assert labelsRegion != null;
OperationStatus[] finalOpStatus = new OperationStatus[authLabels.size()];
List<String> currentAuths = this.getAuths(user, true);
List<String> currentAuths;
if (AccessControlLists.isGroupPrincipal(Bytes.toString(user))) {
String group = AccessControlLists.getGroupName(Bytes.toString(user));
currentAuths = this.getGroupAuths(new String[]{group}, true);
}
else {
currentAuths = this.getUserAuths(user, true);
}
List<Mutation> deletes = new ArrayList<Mutation>(authLabels.size());
int i = 0;
for (byte[] authLabel : authLabels) {
@ -329,17 +324,20 @@ public class DefaultVisibilityLabelServiceImpl implements VisibilityLabelService
}
@Override
public List<String> getAuths(byte[] user, boolean systemCall) throws IOException {
public List<String> getUserAuths(byte[] user, boolean systemCall)
throws IOException {
assert (labelsRegion != null || systemCall);
if (systemCall || labelsRegion == null) {
return this.labelsCache.getAuths(Bytes.toString(user));
return this.labelsCache.getUserAuths(Bytes.toString(user));
}
Scan s = new Scan();
s.addColumn(LABELS_TABLE_FAMILY, user);
if (user != null && user.length > 0) {
s.addColumn(LABELS_TABLE_FAMILY, user);
}
Filter filter = VisibilityUtils.createVisibilityLabelFilter(this.labelsRegion,
new Authorizations(SYSTEM_LABEL));
s.setFilter(filter);
List<String> auths = new ArrayList<String>();
ArrayList<String> auths = new ArrayList<String>();
RegionScanner scanner = this.labelsRegion.getScanner(s);
try {
List<Cell> results = new ArrayList<Cell>(1);
@ -360,6 +358,43 @@ public class DefaultVisibilityLabelServiceImpl implements VisibilityLabelService
return auths;
}
@Override
public List<String> getGroupAuths(String[] groups, boolean systemCall)
throws IOException {
assert (labelsRegion != null || systemCall);
if (systemCall || labelsRegion == null) {
return this.labelsCache.getGroupAuths(groups);
}
Scan s = new Scan();
if (groups != null && groups.length > 0) {
for (String group : groups) {
s.addColumn(LABELS_TABLE_FAMILY, Bytes.toBytes(AccessControlLists.toGroupEntry(group)));
}
}
Filter filter = VisibilityUtils.createVisibilityLabelFilter(this.labelsRegion,
new Authorizations(SYSTEM_LABEL));
s.setFilter(filter);
Set<String> auths = new HashSet<String>();
RegionScanner scanner = this.labelsRegion.getScanner(s);
try {
List<Cell> results = new ArrayList<Cell>(1);
while (true) {
scanner.next(results);
if (results.isEmpty()) break;
Cell cell = results.get(0);
int ordinal = Bytes.toInt(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength());
String label = this.labelsCache.getLabel(ordinal);
if (label != null) {
auths.add(label);
}
results.clear();
}
} finally {
scanner.close();
}
return new ArrayList<String>(auths);
}
@Override
public List<String> listLabels(String regex) throws IOException {
assert (labelsRegion != null);
@ -383,9 +418,11 @@ public class DefaultVisibilityLabelServiceImpl implements VisibilityLabelService
@Override
public List<Tag> createVisibilityExpTags(String visExpression, boolean withSerializationFormat,
boolean checkAuths) throws IOException {
Set<Integer> auths = null;
Set<Integer> auths = new HashSet<Integer>();
if (checkAuths) {
auths = this.labelsCache.getAuthsAsOrdinals(VisibilityUtils.getActiveUser().getShortName());
User user = VisibilityUtils.getActiveUser();
auths.addAll(this.labelsCache.getUserAuthsAsOrdinals(user.getShortName()));
auths.addAll(this.labelsCache.getGroupAuthsAsOrdinals(user.getGroupNames()));
}
return VisibilityUtils.createVisibilityExpTags(visExpression, withSerializationFormat,
checkAuths, auths, labelsCache);
@ -494,26 +531,44 @@ public class DefaultVisibilityLabelServiceImpl implements VisibilityLabelService
}
protected boolean isReadFromSystemAuthUser() throws IOException {
byte[] user = Bytes.toBytes(VisibilityUtils.getActiveUser().getShortName());
User user = VisibilityUtils.getActiveUser();
return havingSystemAuth(user);
}
@Override
public boolean havingSystemAuth(byte[] user) throws IOException {
public boolean havingSystemAuth(User user) throws IOException {
// A super user has 'system' auth.
if (isSystemOrSuperUser(user)) {
return true;
}
// A user can also be explicitly granted 'system' auth.
List<String> auths = this.getAuths(user, true);
List<String> auths = this.getUserAuths(Bytes.toBytes(user.getShortName()), true);
if (LOG.isTraceEnabled()) {
LOG.trace("The auths for user " + Bytes.toString(user) + " are " + auths);
LOG.trace("The auths for user " + user.getShortName() + " are " + auths);
}
if (auths.contains(SYSTEM_LABEL)) {
return true;
}
auths = this.getGroupAuths(user.getGroupNames(), true);
if (LOG.isTraceEnabled()) {
LOG.trace("The auths for groups of user " + user.getShortName() + " are " + auths);
}
return auths.contains(SYSTEM_LABEL);
}
protected boolean isSystemOrSuperUser(byte[] user) throws IOException {
return this.superUsers.contains(Bytes.toString(user));
private boolean isSystemOrSuperUser(User user) throws IOException {
if (this.superUsers.contains(user.getShortName())) {
return true;
}
String[] groups = user.getGroupNames();
if (groups != null && groups.length > 0) {
for (String group : groups) {
if (this.superGroups.contains(group)) {
return true;
}
}
}
return false;
}
@Override

View File

@ -18,7 +18,9 @@
package org.apache.hadoop.hbase.security.visibility;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -60,8 +62,10 @@ public class DefinedSetFilterScanLabelGenerator implements ScanLabelGenerator {
if (authorizations != null) {
List<String> labels = authorizations.getLabels();
String userName = user.getShortName();
List<String> auths = this.labelsCache.getAuths(userName);
return dropLabelsNotInUserAuths(labels, auths, userName);
Set<String> auths = new HashSet<String>();
auths.addAll(this.labelsCache.getUserAuths(userName));
auths.addAll(this.labelsCache.getGroupAuths(user.getGroupNames()));
return dropLabelsNotInUserAuths(labels, new ArrayList<String>(auths), userName);
}
return null;
}

View File

@ -17,7 +17,10 @@
*/
package org.apache.hadoop.hbase.security.visibility;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -59,7 +62,10 @@ public class EnforcingScanLabelGenerator implements ScanLabelGenerator {
if (authorizations != null) {
LOG.warn("Dropping authorizations requested by user " + userName + ": " + authorizations);
}
return this.labelsCache.getAuths(userName);
Set<String> auths = new HashSet<String>();
auths.addAll(this.labelsCache.getUserAuths(userName));
auths.addAll(this.labelsCache.getGroupAuths(user.getGroupNames()));
return new ArrayList<String>(auths);
}
}

View File

@ -17,7 +17,10 @@
*/
package org.apache.hadoop.hbase.security.visibility;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -62,7 +65,10 @@ public class FeedUserAuthScanLabelGenerator implements ScanLabelGenerator {
if (authorizations == null || authorizations.getLabels() == null
|| authorizations.getLabels().isEmpty()) {
String userName = user.getShortName();
return this.labelsCache.getAuths(userName);
Set<String> auths = new HashSet<String>();
auths.addAll(this.labelsCache.getUserAuths(userName));
auths.addAll(this.labelsCache.getGroupAuths(user.getGroupNames()));
return new ArrayList<String>(auths);
}
return authorizations.getLabels();
}

View File

@ -130,7 +130,8 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements
private Map<InternalScanner,String> scannerOwners =
new MapMaker().weakKeys().makeMap();
List<String> superUsers;
private List<String> superUsers;
private List<String> superGroups;
private VisibilityLabelService visibilityLabelService;
// Add to this list if there are any reserved tag types
@ -159,7 +160,10 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements
visibilityLabelService = VisibilityLabelServiceManager.getInstance()
.getVisibilityLabelService(this.conf);
}
this.superUsers = getSystemAndSuperUsers();
Pair<List<String>, List<String>> superUsersAndGroups =
VisibilityUtils.getSystemAndSuperUsers(this.conf);
this.superUsers = superUsersAndGroups.getFirst();
this.superGroups = superUsersAndGroups.getSecond();
}
@Override
@ -632,24 +636,20 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements
}
}
private List<String> getSystemAndSuperUsers() throws IOException {
User user = User.getCurrent();
if (user == null) {
throw new IOException("Unable to obtain the current user, "
+ "authorization checks for internal operations will not work correctly!");
}
if (LOG.isTraceEnabled()) {
LOG.trace("Current user name is "+user.getShortName());
}
String currentUser = user.getShortName();
List<String> superUsers = Lists.asList(currentUser,
this.conf.getStrings(AccessControlLists.SUPERUSER_CONF_KEY, new String[0]));
return superUsers;
}
private boolean isSystemOrSuperUser() throws IOException {
User activeUser = VisibilityUtils.getActiveUser();
return this.superUsers.contains(activeUser.getShortName());
if (this.superUsers.contains(activeUser.getShortName())) {
return true;
}
String[] groups = activeUser.getGroupNames();
if (groups != null && groups.length > 0) {
for (String group : groups) {
if (this.superGroups.contains(group)) {
return true;
}
}
}
return false;
}
@Override
@ -825,7 +825,13 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements
+ (requestingUser != null ? requestingUser.getShortName() : "null")
+ "' is not authorized to perform this action.");
}
labels = this.visibilityLabelService.getAuths(user, false);
if (AccessControlLists.isGroupPrincipal(Bytes.toString(user))) {
String group = AccessControlLists.getGroupName(Bytes.toString(user));
labels = this.visibilityLabelService.getGroupAuths(new String[]{group}, false);
}
else {
labels = this.visibilityLabelService.getUserAuths(user, false);
}
} catch (IOException e) {
ResponseConverter.setControllerException(controller, e);
}
@ -919,7 +925,7 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements
if (user == null) {
throw new IOException("Unable to retrieve calling user");
}
if (!(this.visibilityLabelService.havingSystemAuth(Bytes.toBytes(user.getShortName())))) {
if (!(this.visibilityLabelService.havingSystemAuth(user))) {
throw new AccessDeniedException("User '" + user.getShortName()
+ "' is not authorized to perform this action.");
}

View File

@ -27,6 +27,7 @@ import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.hbase.classification.InterfaceStability;
import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
import org.apache.hadoop.hbase.regionserver.OperationStatus;
import org.apache.hadoop.hbase.security.User;
/**
* The interface which deals with visibility labels and user auths admin service as well as the cell
@ -73,13 +74,24 @@ public interface VisibilityLabelService extends Configurable {
OperationStatus[] clearAuths(byte[] user, List<byte[]> authLabels) throws IOException;
/**
* Retrieve the visibility labels for the user.
* @param user
* Name of the user whose authorization to be retrieved
* @param systemCall
* Whether a system or user originated call.
* @return Visibility labels authorized for the given user.
*/
List<String> getAuths(byte[] user, boolean systemCall) throws IOException;
List<String> getUserAuths(byte[] user, boolean systemCall) throws IOException;
/**
* Retrieve the visibility labels for the groups.
* @param groups
* Name of the groups whose authorization to be retrieved
* @param systemCall
* Whether a system or user originated call.
* @return Visibility labels authorized for the given group.
*/
List<String> getGroupAuths(String[] groups, boolean systemCall) throws IOException;
/**
* Retrieve the list of visibility labels defined in the system.
@ -124,7 +136,7 @@ public interface VisibilityLabelService extends Configurable {
* User for whom system auth check to be done.
* @return true if the given user is having system/super auth
*/
boolean havingSystemAuth(byte[] user) throws IOException;
boolean havingSystemAuth(User user) throws IOException;
/**
* System uses this for deciding whether a Cell can be deleted by matching visibility expression
@ -167,4 +179,5 @@ public interface VisibilityLabelService extends Configurable {
*/
byte[] encodeVisibilityForReplication(final List<Tag> visTags,
final Byte serializationFormat) throws IOException;
}

View File

@ -35,6 +35,7 @@ import org.apache.hadoop.hbase.exceptions.DeserializationException;
import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.MultiUserAuthorizations;
import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.UserAuthorizations;
import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.VisibilityLabel;
import org.apache.hadoop.hbase.security.access.AccessControlLists;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
import org.apache.zookeeper.KeeperException;
@ -57,6 +58,8 @@ public class VisibilityLabelsCache implements VisibilityLabelOrdinalProvider {
private Map<String, Integer> labels = new HashMap<String, Integer>();
private Map<Integer, String> ordinalVsLabels = new HashMap<Integer, String>();
private Map<String, Set<Integer>> userAuths = new HashMap<String, Set<Integer>>();
private Map<String, Set<Integer>> groupAuths = new HashMap<String, Set<Integer>>();
/**
* This covers the members labels, ordinalVsLabels and userAuths
*/
@ -139,9 +142,15 @@ public class VisibilityLabelsCache implements VisibilityLabelOrdinalProvider {
this.lock.writeLock().lock();
try {
this.userAuths.clear();
this.groupAuths.clear();
for (UserAuthorizations userAuths : multiUserAuths.getUserAuthsList()) {
String user = Bytes.toString(userAuths.getUser().toByteArray());
this.userAuths.put(user, new HashSet<Integer>(userAuths.getAuthList()));
if (AccessControlLists.isGroupPrincipal(user)) {
this.groupAuths.put(AccessControlLists.getGroupName(user),
new HashSet<Integer>(userAuths.getAuthList()));
} else {
this.userAuths.put(user, new HashSet<Integer>(userAuths.getAuthList()));
}
}
} finally {
this.lock.writeLock().unlock();
@ -196,30 +205,37 @@ public class VisibilityLabelsCache implements VisibilityLabelOrdinalProvider {
}
}
public List<String> getAuths(String user) {
public List<String> getUserAuths(String user) {
List<String> auths = EMPTY_LIST;
this.lock.readLock().lock();
try {
Set<Integer> authOrdinals = userAuths.get(user);
if (authOrdinals != null) {
auths = new ArrayList<String>(authOrdinals.size());
for (Integer authOrdinal : authOrdinals) {
auths.add(ordinalVsLabels.get(authOrdinal));
}
Set<Integer> authOrdinals = getUserAuthsAsOrdinals(user);
if (!authOrdinals.equals(EMPTY_SET)) {
auths = new ArrayList<String>(authOrdinals.size());
for (Integer authOrdinal : authOrdinals) {
auths.add(ordinalVsLabels.get(authOrdinal));
}
}
return auths;
}
public List<String> getGroupAuths(String[] groups) {
List<String> auths = EMPTY_LIST;
Set<Integer> authOrdinals = getGroupAuthsAsOrdinals(groups);
if (!authOrdinals.equals(EMPTY_SET)) {
auths = new ArrayList<String>(authOrdinals.size());
for (Integer authOrdinal : authOrdinals) {
auths.add(ordinalVsLabels.get(authOrdinal));
}
} finally {
this.lock.readLock().unlock();
}
return auths;
}
/**
* Returns the list of ordinals of authentications associated with the user
* Returns the list of ordinals of labels associated with the user
*
* @param user Not null value.
* @return the list of ordinals
*/
public Set<Integer> getAuthsAsOrdinals(String user) {
public Set<Integer> getUserAuthsAsOrdinals(String user) {
this.lock.readLock().lock();
try {
Set<Integer> auths = userAuths.get(user);
@ -229,6 +245,31 @@ public class VisibilityLabelsCache implements VisibilityLabelOrdinalProvider {
}
}
/**
* Returns the list of ordinals of labels associated with the groups
*
* @param groups
* @return the list of ordinals
*/
public Set<Integer> getGroupAuthsAsOrdinals(String[] groups) {
this.lock.readLock().lock();
try {
Set<Integer> authOrdinals = new HashSet<Integer>();
if (groups != null && groups.length > 0) {
Set<Integer> groupAuthOrdinals = null;
for (String group : groups) {
groupAuthOrdinals = groupAuths.get(group);
if (groupAuthOrdinals != null && !groupAuthOrdinals.isEmpty()) {
authOrdinals.addAll(groupAuthOrdinals);
}
}
}
return (authOrdinals.isEmpty()) ? EMPTY_SET : authOrdinals;
} finally {
this.lock.readLock().unlock();
}
}
public void writeToZookeeper(byte[] data, boolean labelsOrUserAuths) {
this.zkVisibilityWatcher.writeToZookeeper(data, labelsOrUserAuths);
}

View File

@ -53,6 +53,7 @@ import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.Visibil
import org.apache.hadoop.hbase.regionserver.HRegion;
import org.apache.hadoop.hbase.security.AccessDeniedException;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.security.access.AccessControlLists;
import org.apache.hadoop.hbase.security.visibility.expression.ExpressionNode;
import org.apache.hadoop.hbase.security.visibility.expression.LeafExpressionNode;
import org.apache.hadoop.hbase.security.visibility.expression.NonLeafExpressionNode;
@ -60,6 +61,7 @@ import org.apache.hadoop.hbase.security.visibility.expression.Operator;
import org.apache.hadoop.hbase.util.ByteRange;
import org.apache.hadoop.hbase.util.ByteStringer;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.util.SimpleMutableByteRange;
import org.apache.hadoop.util.ReflectionUtils;
@ -100,6 +102,38 @@ public class VisibilityUtils {
return ProtobufUtil.prependPBMagic(visReqBuilder.build().toByteArray());
}
/**
* Get the super users and groups defined in the configuration.
* The user running the hbase server is always included.
* @param conf
* @return Pair of super user list and super group list.
* @throws IOException
*/
public static Pair<List<String>, List<String>> getSystemAndSuperUsers(Configuration conf)
throws IOException {
ArrayList<String> superUsers = new ArrayList<String>();
ArrayList<String> superGroups = new ArrayList<String>();
User user = User.getCurrent();
if (user == null) {
throw new IOException("Unable to obtain the current user, "
+ "authorization checks for internal operations will not work correctly!");
}
if (LOG.isTraceEnabled()) {
LOG.trace("Current user name is " + user.getShortName());
}
String currentUser = user.getShortName();
String[] superUserList = conf.getStrings(AccessControlLists.SUPERUSER_CONF_KEY, new String[0]);
for (String name : superUserList) {
if (AccessControlLists.isGroupPrincipal(name)) {
superGroups.add(AccessControlLists.getGroupName(name));
} else {
superUsers.add(name);
}
}
superUsers.add(currentUser);
return new Pair<List<String>, List<String>>(superUsers, superGroups);
}
/**
* Creates the user auth data to be written to zookeeper.
* @param userAuths

View File

@ -27,8 +27,10 @@ import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -82,6 +84,7 @@ public class ExpAsStringVisibilityLabelServiceImpl implements VisibilityLabelSer
private HRegion labelsRegion;
private List<ScanLabelGenerator> scanLabelGenerators;
private List<String> superUsers;
private List<String> superGroups;
@Override
public OperationStatus[] addLabels(List<byte[]> labels) throws IOException {
@ -115,7 +118,14 @@ public class ExpAsStringVisibilityLabelServiceImpl implements VisibilityLabelSer
public OperationStatus[] clearAuths(byte[] user, List<byte[]> authLabels) throws IOException {
assert labelsRegion != null;
OperationStatus[] finalOpStatus = new OperationStatus[authLabels.size()];
List<String> currentAuths = this.getAuths(user, true);
List<String> currentAuths;
if (AccessControlLists.isGroupPrincipal(Bytes.toString(user))) {
String group = AccessControlLists.getGroupName(Bytes.toString(user));
currentAuths = this.getGroupAuths(new String[]{group}, true);
}
else {
currentAuths = this.getUserAuths(user, true);
}
Delete d = new Delete(user);
int i = 0;
for (byte[] authLabel : authLabels) {
@ -141,7 +151,7 @@ public class ExpAsStringVisibilityLabelServiceImpl implements VisibilityLabelSer
}
@Override
public List<String> getAuths(byte[] user, boolean systemCall) throws IOException {
public List<String> getUserAuths(byte[] user, boolean systemCall) throws IOException {
assert (labelsRegion != null || systemCall);
List<String> auths = new ArrayList<String>();
Get get = new Get(user);
@ -168,13 +178,47 @@ public class ExpAsStringVisibilityLabelServiceImpl implements VisibilityLabelSer
if (cells != null) {
for (Cell cell : cells) {
String auth = Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(),
cell.getQualifierLength());
cell.getQualifierLength());
auths.add(auth);
}
}
return auths;
}
@Override
public List<String> getGroupAuths(String[] groups, boolean systemCall) throws IOException {
assert (labelsRegion != null || systemCall);
List<String> auths = new ArrayList<String>();
if (groups != null && groups.length > 0) {
for (String group : groups) {
Get get = new Get(Bytes.toBytes(AccessControlLists.toGroupEntry(group)));
List<Cell> cells = null;
if (labelsRegion == null) {
Table table = null;
try {
table = new HTable(conf, VisibilityConstants.LABELS_TABLE_NAME);
Result result = table.get(get);
cells = result.listCells();
} finally {
if (table != null) {
table.close();
}
}
} else {
cells = this.labelsRegion.get(get, false);
}
if (cells != null) {
for (Cell cell : cells) {
String auth = Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(),
cell.getQualifierLength());
auths.add(auth);
}
}
}
}
return auths;
}
@Override
public List<String> listLabels(String regex) throws IOException {
// return an empty list for this implementation.
@ -282,7 +326,7 @@ public class ExpAsStringVisibilityLabelServiceImpl implements VisibilityLabelSer
}
protected boolean isReadFromSystemAuthUser() throws IOException {
byte[] user = Bytes.toBytes(VisibilityUtils.getActiveUser().getShortName());
User user = VisibilityUtils.getActiveUser();
return havingSystemAuth(user);
}
@ -347,13 +391,15 @@ public class ExpAsStringVisibilityLabelServiceImpl implements VisibilityLabelSer
@Override
public void init(RegionCoprocessorEnvironment e) throws IOException {
this.scanLabelGenerators = VisibilityUtils.getScanLabelGenerators(this.conf);
this.superUsers = getSystemAndSuperUsers();
initSystemAndSuperUsers();
if (e.getRegion().getRegionInfo().getTable().equals(LABELS_TABLE_NAME)) {
this.labelsRegion = e.getRegion();
}
}
private List<String> getSystemAndSuperUsers() throws IOException {
private void initSystemAndSuperUsers() throws IOException {
this.superUsers = new ArrayList<String>();
this.superGroups = new ArrayList<String>();
User user = User.getCurrent();
if (user == null) {
throw new IOException("Unable to obtain the current user, "
@ -363,21 +409,42 @@ public class ExpAsStringVisibilityLabelServiceImpl implements VisibilityLabelSer
LOG.trace("Current user name is " + user.getShortName());
}
String currentUser = user.getShortName();
List<String> superUsers = Lists.asList(currentUser,
List<String> superUserList = Lists.asList(currentUser,
this.conf.getStrings(AccessControlLists.SUPERUSER_CONF_KEY, new String[0]));
return superUsers;
if (superUserList != null) {
for (String name : superUserList) {
if (AccessControlLists.isGroupPrincipal(name)) {
this.superGroups.add(AccessControlLists.getGroupName(name));
} else {
this.superUsers.add(name);
}
}
};
}
protected boolean isSystemOrSuperUser(byte[] user) throws IOException {
return this.superUsers.contains(Bytes.toString(user));
protected boolean isSystemOrSuperUser(User user) throws IOException {
if (this.superUsers.contains(user.getShortName())) {
return true;
}
String[] groups = user.getGroupNames();
if (groups != null) {
for (String group : groups) {
if (this.superGroups.contains(group)) {
return true;
}
}
}
return false;
}
@Override
public boolean havingSystemAuth(byte[] user) throws IOException {
public boolean havingSystemAuth(User user) throws IOException {
if (isSystemOrSuperUser(user)) {
return true;
}
List<String> auths = this.getAuths(user, true);
Set<String> auths = new HashSet<String>();
auths.addAll(this.getUserAuths(Bytes.toBytes(user.getShortName()), true));
auths.addAll(this.getGroupAuths(user.getGroupNames(), true));
return auths.contains(SYSTEM_LABEL);
}

View File

@ -0,0 +1,346 @@
/**
* 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.hbase.security.visibility;
import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_NAME;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellScanner;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.GetAuthsResponse;
import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsResponse;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.testclassification.MediumTests;
import org.apache.hadoop.hbase.testclassification.SecurityTests;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.TestName;
import com.google.protobuf.ByteString;
@Category({SecurityTests.class, MediumTests.class})
public class TestVisibilityLablesWithGroups {
public static final String CONFIDENTIAL = "confidential";
private static final String SECRET = "secret";
public static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
private static final byte[] ROW_1 = Bytes.toBytes("row1");
private final static byte[] CF = Bytes.toBytes("f");
private final static byte[] Q1 = Bytes.toBytes("q1");
private final static byte[] Q2 = Bytes.toBytes("q2");
private final static byte[] Q3 = Bytes.toBytes("q3");
private final static byte[] value1 = Bytes.toBytes("value1");
private final static byte[] value2 = Bytes.toBytes("value2");
private final static byte[] value3 = Bytes.toBytes("value3");
public static Configuration conf;
@Rule
public final TestName TEST_NAME = new TestName();
public static User SUPERUSER;
public static User TESTUSER;
@BeforeClass
public static void setupBeforeClass() throws Exception {
// setup configuration
conf = TEST_UTIL.getConfiguration();
VisibilityTestUtil.enableVisiblityLabels(conf);
// Not setting any SLG class. This means to use the default behavior.
// Use a group as the super user.
conf.set("hbase.superuser", "@supergroup");
TEST_UTIL.startMiniCluster(1);
// 'admin' has super user permission because it is part of the 'supergroup'
SUPERUSER = User.createUserForTesting(conf, "admin", new String[] { "supergroup" });
// 'test' user will inherit 'testgroup' visibility labels
TESTUSER = User.createUserForTesting(conf, "test", new String[] {"testgroup" });
// Wait for the labels table to become available
TEST_UTIL.waitTableEnabled(LABELS_TABLE_NAME.getName(), 50000);
// Set up for the test
SUPERUSER.runAs(new PrivilegedExceptionAction<Void>() {
public Void run() throws Exception {
try {
VisibilityClient.addLabels(conf, new String[] { SECRET, CONFIDENTIAL });
// set auth for @testgroup
VisibilityClient.setAuths(conf, new String[] { CONFIDENTIAL }, "@testgroup");
} catch (Throwable t) {
throw new IOException(t);
}
return null;
}
});
}
@Test
public void testGroupAuths() throws Exception {
final TableName tableName = TableName.valueOf(TEST_NAME.getMethodName());
// create the table and put data.
SUPERUSER.runAs(new PrivilegedExceptionAction<Void>() {
public Void run() throws Exception {
Table table = TEST_UTIL.createTable(tableName, CF);
try {
Put put = new Put(ROW_1);
put.add(CF, Q1, HConstants.LATEST_TIMESTAMP, value1);
put.setCellVisibility(new CellVisibility(SECRET));
table.put(put);
put = new Put(ROW_1);
put.add(CF, Q2, HConstants.LATEST_TIMESTAMP, value2);
put.setCellVisibility(new CellVisibility(CONFIDENTIAL));
table.put(put);
put = new Put(ROW_1);
put.add(CF, Q3, HConstants.LATEST_TIMESTAMP, value3);
table.put(put);
} finally {
table.close();
}
return null;
}
});
// 'admin' user is part of 'supergroup', thus can see all the cells.
SUPERUSER.runAs(new PrivilegedExceptionAction<Void>() {
public Void run() throws Exception {
Connection connection = ConnectionFactory.createConnection(conf);
Table table = connection.getTable(tableName);
try {
Scan s = new Scan();
ResultScanner scanner = table.getScanner(s);
Result[] next = scanner.next(1);
// Test that super user can see all the cells.
assertTrue(next.length == 1);
CellScanner cellScanner = next[0].cellScanner();
cellScanner.advance();
Cell current = cellScanner.current();
assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(),
current.getRowLength(), ROW_1, 0, ROW_1.length));
assertTrue(Bytes.equals(current.getQualifier(), Q1));
assertTrue(Bytes.equals(current.getValue(), value1));
cellScanner.advance();
current = cellScanner.current();
assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(),
current.getRowLength(), ROW_1, 0, ROW_1.length));
assertTrue(Bytes.equals(current.getQualifier(), Q2));
assertTrue(Bytes.equals(current.getValue(), value2));
cellScanner.advance();
current = cellScanner.current();
assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(),
current.getRowLength(), ROW_1, 0, ROW_1.length));
assertTrue(Bytes.equals(current.getQualifier(), Q3));
assertTrue(Bytes.equals(current.getValue(), value3));
} finally {
table.close();
connection.close();
}
return null;
}
});
// Get testgroup's labels.
SUPERUSER.runAs(new PrivilegedExceptionAction<Void>() {
public Void run() throws Exception {
GetAuthsResponse authsResponse = null;
try {
authsResponse = VisibilityClient.getAuths(conf, "@testgroup");
} catch (Throwable e) {
fail("Should not have failed");
}
List<String> authsList = new ArrayList<String>();
for (ByteString authBS : authsResponse.getAuthList()) {
authsList.add(Bytes.toString(authBS.toByteArray()));
}
assertEquals(1, authsList.size());
assertTrue(authsList.contains(CONFIDENTIAL));
return null;
}
});
// Test that test user can see what 'testgroup' has been authorized to.
TESTUSER.runAs(new PrivilegedExceptionAction<Void>() {
public Void run() throws Exception {
Connection connection = ConnectionFactory.createConnection(conf);
Table table = connection.getTable(tableName);
try {
// Test scan with no auth attribute
Scan s = new Scan();
ResultScanner scanner = table.getScanner(s);
Result[] next = scanner.next(1);
assertTrue(next.length == 1);
CellScanner cellScanner = next[0].cellScanner();
cellScanner.advance();
Cell current = cellScanner.current();
// test user can see value2 (CONFIDENTIAL) and value3 (no label)
assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(),
current.getRowLength(), ROW_1, 0, ROW_1.length));
assertTrue(Bytes.equals(current.getQualifier(), Q2));
assertTrue(Bytes.equals(current.getValue(), value2));
cellScanner.advance();
current = cellScanner.current();
// test user can see value2 (CONFIDENTIAL) and value3 (no label)
assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(),
current.getRowLength(), ROW_1, 0, ROW_1.length));
assertTrue(Bytes.equals(current.getQualifier(), Q3));
assertTrue(Bytes.equals(current.getValue(), value3));
// Test scan with correct auth attribute for test user
Scan s1 = new Scan();
// test user is entitled to 'CONFIDENTIAL'.
// If we set both labels in the scan, 'SECRET' will be dropped by the SLGs.
s1.setAuthorizations(new Authorizations(new String[] { SECRET, CONFIDENTIAL }));
ResultScanner scanner1 = table.getScanner(s1);
Result[] next1 = scanner1.next(1);
assertTrue(next1.length == 1);
CellScanner cellScanner1 = next1[0].cellScanner();
cellScanner1.advance();
Cell current1 = cellScanner1.current();
// test user can see value2 (CONFIDENTIAL) and value3 (no label)
assertTrue(Bytes.equals(current1.getRowArray(), current1.getRowOffset(),
current1.getRowLength(), ROW_1, 0, ROW_1.length));
assertTrue(Bytes.equals(current1.getQualifier(), Q2));
assertTrue(Bytes.equals(current1.getValue(), value2));
cellScanner1.advance();
current1 = cellScanner1.current();
// test user can see value2 (CONFIDENTIAL) and value3 (no label)
assertTrue(Bytes.equals(current1.getRowArray(), current1.getRowOffset(),
current1.getRowLength(), ROW_1, 0, ROW_1.length));
assertTrue(Bytes.equals(current1.getQualifier(), Q3));
assertTrue(Bytes.equals(current1.getValue(), value3));
// Test scan with incorrect auth attribute for test user
Scan s2 = new Scan();
// test user is entitled to 'CONFIDENTIAL'.
// If we set 'SECRET', it will be dropped by the SLGs.
s2.setAuthorizations(new Authorizations(new String[] { SECRET }));
ResultScanner scanner2 = table.getScanner(s2);
Result next2 = scanner2.next();
CellScanner cellScanner2 = next2.cellScanner();
cellScanner2.advance();
Cell current2 = cellScanner2.current();
// This scan will only see value3 (no label)
assertTrue(Bytes.equals(current2.getRowArray(), current2.getRowOffset(),
current2.getRowLength(), ROW_1, 0, ROW_1.length));
assertTrue(Bytes.equals(current2.getQualifier(), Q3));
assertTrue(Bytes.equals(current2.getValue(), value3));
assertFalse(cellScanner2.advance());
} finally {
table.close();
connection.close();
}
return null;
}
});
// Clear 'testgroup' of CONFIDENTIAL label.
SUPERUSER.runAs(new PrivilegedExceptionAction<Void>() {
public Void run() throws Exception {
VisibilityLabelsResponse response = null;
try {
response = VisibilityClient.clearAuths(conf, new String[] { CONFIDENTIAL }, "@testgroup");
} catch (Throwable e) {
fail("Should not have failed");
}
return null;
}
});
// Get testgroup's labels. No label is returned.
SUPERUSER.runAs(new PrivilegedExceptionAction<Void>() {
public Void run() throws Exception {
GetAuthsResponse authsResponse = null;
try {
authsResponse = VisibilityClient.getAuths(conf, "@testgroup");
} catch (Throwable e) {
fail("Should not have failed");
}
List<String> authsList = new ArrayList<String>();
for (ByteString authBS : authsResponse.getAuthList()) {
authsList.add(Bytes.toString(authBS.toByteArray()));
}
assertEquals(0, authsList.size());
return null;
}
});
// Test that test user cannot see the cells with the labels anymore.
TESTUSER.runAs(new PrivilegedExceptionAction<Void>() {
public Void run() throws Exception {
Connection connection = ConnectionFactory.createConnection(conf);
Table table = connection.getTable(tableName);
try {
Scan s1 = new Scan();
// test user is not entitled to 'CONFIDENTIAL' anymore since we dropped
// testgroup's label. test user has no auth labels now.
// scan's labels will be dropped on the server side.
s1.setAuthorizations(new Authorizations(new String[] { SECRET, CONFIDENTIAL }));
ResultScanner scanner1 = table.getScanner(s1);
Result[] next1 = scanner1.next(1);
assertTrue(next1.length == 1);
CellScanner cellScanner1 = next1[0].cellScanner();
cellScanner1.advance();
Cell current1 = cellScanner1.current();
// test user can only see value3 (no label)
assertTrue(Bytes.equals(current1.getRowArray(), current1.getRowOffset(),
current1.getRowLength(), ROW_1, 0, ROW_1.length));
assertTrue(Bytes.equals(current1.getQualifier(), Q3));
assertTrue(Bytes.equals(current1.getValue(), value3));
assertFalse(cellScanner1.advance());
} finally {
table.close();
connection.close();
}
return null;
}
});
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
TEST_UTIL.shutdownMiniCluster();
}
}