HBASE-13235 Revisit the security auditing semantics (Srikanth Srungarapu)

This commit is contained in:
Matteo Bertozzi 2015-03-19 07:01:21 +00:00
parent 014b812103
commit 535ebbfde9
3 changed files with 166 additions and 12 deletions

View File

@ -241,6 +241,19 @@ public final class TableName implements Comparable<TableName> {
return namespaceAsString; return namespaceAsString;
} }
/**
* Ideally, getNameAsString should contain namespace within it,
* but if the namespace is default, it just returns the name. This method
* takes care of this corner case.
*/
public String getNameWithNamespaceInclAsString() {
if(getNamespaceAsString().equals(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR)) {
return NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR +
TableName.NAMESPACE_DELIM + getNameAsString();
}
return getNameAsString();
}
public byte[] getQualifier() { public byte[] getQualifier() {
return qualifier; return qualifier;
} }

View File

@ -431,6 +431,39 @@ public class AccessController extends BaseMasterAndRegionObserver
} }
} }
/**
* Authorizes that the current user has any of the given permissions for the
* given table, column family and column qualifier.
* @param tableName Table requested
* @param family Column family param
* @param qualifier Column qualifier param
* @throws IOException if obtaining the current user fails
* @throws AccessDeniedException if user has no authorization
*/
private void requireTablePermission(String request, TableName tableName, byte[] family,
byte[] qualifier, Action... permissions) throws IOException {
User user = getActiveUser();
AuthResult result = null;
for (Action permission : permissions) {
if (authManager.authorize(user, tableName, null, null, permission)) {
result = AuthResult.allow(request, "Table permission granted", user,
permission, tableName, null, null);
result.getParams().setFamily(family).setQualifier(qualifier);
break;
} else {
// rest of the world
result = AuthResult.deny(request, "Insufficient permissions", user,
permission, tableName, family, qualifier);
result.getParams().setFamily(family).setQualifier(qualifier);
}
}
logResult(result);
if (!result.isAllowed()) {
throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
}
}
/** /**
* Authorizes that the current user has any of the given permissions to access the table. * Authorizes that the current user has any of the given permissions to access the table.
* *
@ -507,10 +540,15 @@ public class AccessController extends BaseMasterAndRegionObserver
private void requireGlobalPermission(String request, Action perm, TableName tableName, private void requireGlobalPermission(String request, Action perm, TableName tableName,
Map<byte[], ? extends Collection<byte[]>> familyMap) throws IOException { Map<byte[], ? extends Collection<byte[]>> familyMap) throws IOException {
User user = getActiveUser(); User user = getActiveUser();
AuthResult result = null;
if (authManager.authorize(user, perm)) { if (authManager.authorize(user, perm)) {
logResult(AuthResult.allow(request, "Global check allowed", user, perm, tableName, familyMap)); result = AuthResult.allow(request, "Global check allowed", user, perm, tableName, familyMap);
result.getParams().setTableName(tableName).setFamilies(familyMap);
logResult(result);
} else { } else {
logResult(AuthResult.deny(request, "Global check failed", user, perm, tableName, familyMap)); result = AuthResult.deny(request, "Global check failed", user, perm, tableName, familyMap);
result.getParams().setTableName(tableName).setFamilies(familyMap);
logResult(result);
throw new AccessDeniedException("Insufficient permissions for user '" + throw new AccessDeniedException("Insufficient permissions for user '" +
(user != null ? user.getShortName() : "null") +"' (global, action=" + (user != null ? user.getShortName() : "null") +"' (global, action=" +
perm.toString() + ")"); perm.toString() + ")");
@ -527,10 +565,15 @@ public class AccessController extends BaseMasterAndRegionObserver
private void requireGlobalPermission(String request, Action perm, private void requireGlobalPermission(String request, Action perm,
String namespace) throws IOException { String namespace) throws IOException {
User user = getActiveUser(); User user = getActiveUser();
AuthResult authResult = null;
if (authManager.authorize(user, perm)) { if (authManager.authorize(user, perm)) {
logResult(AuthResult.allow(request, "Global check allowed", user, perm, namespace)); authResult = AuthResult.allow(request, "Global check allowed", user, perm, null);
authResult.getParams().setNamespace(namespace);
logResult(authResult);
} else { } else {
logResult(AuthResult.deny(request, "Global check failed", user, perm, namespace)); authResult = AuthResult.deny(request, "Global check failed", user, perm, null);
authResult.getParams().setNamespace(namespace);
logResult(authResult);
throw new AccessDeniedException("Insufficient permissions for user '" + throw new AccessDeniedException("Insufficient permissions for user '" +
(user != null ? user.getShortName() : "null") +"' (global, action=" + (user != null ? user.getShortName() : "null") +"' (global, action=" +
perm.toString() + ")"); perm.toString() + ")");
@ -565,6 +608,37 @@ public class AccessController extends BaseMasterAndRegionObserver
} }
} }
/**
* Checks that the user has the given global or namespace permission.
* @param namespace
* @param permissions Actions being requested
*/
public void requireNamespacePermission(String request, String namespace, TableName tableName,
Map<byte[], ? extends Collection<byte[]>> familyMap, Action... permissions)
throws IOException {
User user = getActiveUser();
AuthResult result = null;
for (Action permission : permissions) {
if (authManager.authorize(user, namespace, permission)) {
result = AuthResult.allow(request, "Namespace permission granted",
user, permission, namespace);
result.getParams().setTableName(tableName).setFamilies(familyMap);
break;
} else {
// rest of the world
result = AuthResult.deny(request, "Insufficient permissions", user,
permission, namespace);
result.getParams().setTableName(tableName).setFamilies(familyMap);
}
}
logResult(result);
if (!result.isAllowed()) {
throw new AccessDeniedException("Insufficient permissions "
+ result.toContextString());
}
}
/** /**
* Returns <code>true</code> if the current user is allowed the given action * Returns <code>true</code> if the current user is allowed the given action
* over at least one of the column qualifiers in the given column families. * over at least one of the column qualifiers in the given column families.
@ -919,7 +993,8 @@ public class AccessController extends BaseMasterAndRegionObserver
for (byte[] family: families) { for (byte[] family: families) {
familyMap.put(family, null); familyMap.put(family, null);
} }
requireNamespacePermission("createTable", desc.getTableName().getNamespaceAsString(), Action.CREATE); requireNamespacePermission("createTable", desc.getTableName().getNamespaceAsString(),
desc.getTableName(), familyMap, Action.CREATE);
} }
@Override @Override
@ -1050,7 +1125,8 @@ public class AccessController extends BaseMasterAndRegionObserver
@Override @Override
public void preAddColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName, public void preAddColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
HColumnDescriptor column) throws IOException { HColumnDescriptor column) throws IOException {
requirePermission("addColumn", tableName, null, null, Action.ADMIN, Action.CREATE); requireTablePermission("addColumn", tableName, column.getName(), null, Action.ADMIN,
Action.CREATE);
} }
@Override @Override

View File

@ -27,6 +27,8 @@ import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Bytes;
import com.google.common.base.Joiner;
/** /**
* Represents the result of an authorization check for logging and error * Represents the result of an authorization check for logging and error
* reporting. * reporting.
@ -40,6 +42,7 @@ public class AuthResult {
private final String request; private final String request;
private String reason; private String reason;
private final User user; private final User user;
private AuthResult.Params params;
// "family" and "qualifier" should only be used if "families" is null. // "family" and "qualifier" should only be used if "families" is null.
private final byte[] family; private final byte[] family;
@ -58,6 +61,7 @@ public class AuthResult {
this.action = action; this.action = action;
this.families = null; this.families = null;
this.namespace = null; this.namespace = null;
this.params = new Params().setTableName(table).setFamily(family).setQualifier(qualifier);
} }
public AuthResult(boolean allowed, String request, String reason, User user, public AuthResult(boolean allowed, String request, String reason, User user,
@ -73,6 +77,7 @@ public class AuthResult {
this.action = action; this.action = action;
this.families = families; this.families = families;
this.namespace = null; this.namespace = null;
this.params = new Params().setTableName(table).setFamilies(families);
} }
public AuthResult(boolean allowed, String request, String reason, User user, public AuthResult(boolean allowed, String request, String reason, User user,
@ -87,6 +92,7 @@ public class AuthResult {
this.family = null; this.family = null;
this.qualifier = null; this.qualifier = null;
this.families = null; this.families = null;
this.params = new Params().setNamespace(namespace);
} }
public boolean isAllowed() { public boolean isAllowed() {
@ -121,6 +127,8 @@ public class AuthResult {
return request; return request;
} }
public Params getParams() { return this.params;}
public void setAllowed(boolean allowed) { public void setAllowed(boolean allowed) {
this.allowed = allowed; this.allowed = allowed;
} }
@ -129,7 +137,8 @@ public class AuthResult {
this.reason = reason; this.reason = reason;
} }
String toFamilyString() { private static String toFamiliesString(Map<byte[], ? extends Collection<?>> families,
byte[] family, byte[] qual) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
if (families != null) { if (families != null) {
boolean first = true; boolean first = true;
@ -164,8 +173,8 @@ public class AuthResult {
} }
} else if (family != null) { } else if (family != null) {
sb.append(Bytes.toString(family)); sb.append(Bytes.toString(family));
if (qualifier != null) { if (qual != null) {
sb.append(":").append(Bytes.toString(qualifier)); sb.append(":").append(Bytes.toString(qual));
} }
} }
return sb.toString(); return sb.toString();
@ -173,17 +182,25 @@ public class AuthResult {
public String toContextString() { public String toContextString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
String familiesString = toFamiliesString(families, family, qualifier);
sb.append("(user=") sb.append("(user=")
.append(user != null ? user.getName() : "UNKNOWN") .append(user != null ? user.getName() : "UNKNOWN")
.append(", "); .append(", ");
sb.append("scope=") sb.append("scope=")
.append(namespace != null ? namespace : table == null ? "GLOBAL" : table) .append(namespace != null ? namespace :
table == null ? "GLOBAL" : table.getNameWithNamespaceInclAsString())
.append(", "); .append(", ");
if(namespace == null) { if(namespace == null && familiesString.length() > 0) {
sb.append("family=") sb.append("family=")
.append(toFamilyString()) .append(familiesString)
.append(", "); .append(", ");
} }
String paramsString = params.toString();
if(paramsString.length() > 0) {
sb.append("params=[")
.append(paramsString)
.append("],");
}
sb.append("action=") sb.append("action=")
.append(action != null ? action.toString() : "") .append(action != null ? action.toString() : "")
.append(")"); .append(")");
@ -225,4 +242,52 @@ public class AuthResult {
Map<byte[], ? extends Collection<?>> families) { Map<byte[], ? extends Collection<?>> families) {
return new AuthResult(false, request, reason, user, action, table, families); return new AuthResult(false, request, reason, user, action, table, families);
} }
public String toFamilyString() {
return toFamiliesString(families, family, qualifier);
}
public static class Params {
private String namespace = null;
private TableName tableName = null;
private Map<byte[], ? extends Collection<?>> families = null;
byte[] family = null;
byte[] qualifier = null;
public Params setNamespace(String namespace) {
this.namespace = namespace;
return this;
}
public Params setTableName(TableName table) {
this.tableName = table;
return this;
}
public Params setFamilies(Map<byte[], ? extends Collection<?>> families) {
this.families = families;
return this;
}
public Params setFamily(byte[] family) {
this.family = family;
return this;
}
public Params setQualifier(byte[] qualifier) {
this.qualifier = qualifier;
return this;
}
public String toString() {
String familiesString = toFamiliesString(families, family, qualifier);
String[] params = new String[] {
namespace != null ? "namespace=" + namespace : null,
tableName != null ? "table=" + tableName.getNameWithNamespaceInclAsString() : null,
familiesString.length() > 0 ? "family=" + familiesString : null
};
return Joiner.on(",").skipNulls().join(params);
}
}
} }