SOLR-10814 Add short-name feature to RuleBasedAuthz plugin

Additional-Author: Hrishikesh Gadre <hgadre@cloudera.com>
This commit is contained in:
Mike Drob 2020-05-07 14:02:36 -05:00
parent 06b1f3e866
commit d3f4b21deb
No known key found for this signature in database
GPG Key ID: 3E48C0C6EF362B9E
15 changed files with 415 additions and 207 deletions

View File

@ -96,11 +96,20 @@ public abstract class AuthenticationPlugin implements SolrInfoBean {
} }
HttpServletRequest wrapWithPrincipal(HttpServletRequest request, Principal principal) { HttpServletRequest wrapWithPrincipal(HttpServletRequest request, Principal principal) {
return wrapWithPrincipal(request, principal, principal.getName());
}
HttpServletRequest wrapWithPrincipal(HttpServletRequest request, Principal principal, String username) {
return new HttpServletRequestWrapper(request) { return new HttpServletRequestWrapper(request) {
@Override @Override
public Principal getUserPrincipal() { public Principal getUserPrincipal() {
return principal; return principal;
} }
@Override
public String getRemoteUser() {
return username;
}
}; };
} }

View File

@ -41,13 +41,35 @@ public abstract class AuthorizationContext {
} }
public abstract SolrParams getParams() ; public abstract SolrParams getParams() ;
/**
* This method returns the {@link Principal} corresponding to
* the authenticated user for the current request. The value returned by
* {@link Principal#getName()} depends on the authentication mechanism
* used (e.g. for user "foo" with BASIC authentication the result would be
* "foo". On the other hand with KERBEROS it would be foo@REALMNAME).
* The {@link #getUserName()} method may be preferred to extract the identity
* of the authenticated user instead of this method.
*
* @return user principal in case of an authenticated request
* null in case of unauthenticated request
*/
public abstract Principal getUserPrincipal() ; public abstract Principal getUserPrincipal() ;
/**
* This method returns the name of the authenticated user for the current request.
* The return value of this method is agnostic of the underlying authentication
* mechanism used.
*
* @return user name in case of an authenticated user
* null in case of unauthenticated request
*/
public abstract String getUserName();
public abstract String getHttpHeader(String header); public abstract String getHttpHeader(String header);
@SuppressWarnings({"rawtypes"}) public abstract Enumeration<String> getHeaderNames();
public abstract Enumeration getHeaderNames();
public abstract String getRemoteAddr(); public abstract String getRemoteAddr();

View File

@ -145,7 +145,7 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
return false; return false;
} else { } else {
Principal principal = new BasicAuthUserPrincipal(username, pwd); Principal principal = new BasicAuthUserPrincipal(username, pwd);
request = wrapWithPrincipal(request, principal); request = wrapWithPrincipal(request, principal, username);
numAuthenticated.inc(); numAuthenticated.inc();
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
return true; return true;

View File

@ -17,7 +17,6 @@
package org.apache.solr.security; package org.apache.solr.security;
import java.io.IOException; import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -49,8 +48,6 @@ import org.apache.solr.common.cloud.ZkCredentialsProvider;
import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.ACL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* This is an authentication filter based on Hadoop's {@link DelegationTokenAuthenticationFilter}. * This is an authentication filter based on Hadoop's {@link DelegationTokenAuthenticationFilter}.
@ -58,8 +55,6 @@ import org.slf4j.LoggerFactory;
* application to reuse the authentication of an end-user or another application. * application to reuse the authentication of an end-user or another application.
*/ */
public class DelegationTokenKerberosFilter extends DelegationTokenAuthenticationFilter { public class DelegationTokenKerberosFilter extends DelegationTokenAuthenticationFilter {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private CuratorFramework curatorFramework; private CuratorFramework curatorFramework;
private final Locale defaultLocale = Locale.getDefault(); private final Locale defaultLocale = Locale.getDefault();
@ -83,13 +78,12 @@ public class DelegationTokenKerberosFilter extends DelegationTokenAuthentication
* "solr.impersonator.user.name" will be added to the configuration. * "solr.impersonator.user.name" will be added to the configuration.
*/ */
@Override @Override
protected Configuration getProxyuserConfiguration(FilterConfig filterConf) protected Configuration getProxyuserConfiguration(FilterConfig filterConf) {
throws ServletException {
Configuration conf = new Configuration(false); Configuration conf = new Configuration(false);
Enumeration<?> names = filterConf.getInitParameterNames(); Enumeration<String> names = filterConf.getInitParameterNames();
while (names.hasMoreElements()) { while (names.hasMoreElements()) {
String name = (String) names.nextElement(); String name = names.nextElement();
if (name.startsWith(KerberosPlugin.IMPERSONATOR_PREFIX)) { if (name.startsWith(KerberosPlugin.IMPERSONATOR_PREFIX)) {
String value = filterConf.getInitParameter(name); String value = filterConf.getInitParameter(name);
conf.set(PROXYUSER_PREFIX + "." + name.substring(KerberosPlugin.IMPERSONATOR_PREFIX.length()), value); conf.set(PROXYUSER_PREFIX + "." + name.substring(KerberosPlugin.IMPERSONATOR_PREFIX.length()), value);
@ -163,11 +157,8 @@ public class DelegationTokenKerberosFilter extends DelegationTokenAuthentication
// without the appropriate ACL configuration. This issue is possibly related to HADOOP-11973 // without the appropriate ACL configuration. This issue is possibly related to HADOOP-11973
try { try {
zkClient.makePath(SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH, CreateMode.PERSISTENT, true); zkClient.makePath(SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH, CreateMode.PERSISTENT, true);
} catch (KeeperException.NodeExistsException ex) {
} catch (KeeperException ex) { // ignore?
if (ex.code() != KeeperException.Code.NODEEXISTS) {
throw ex;
}
} }
curatorFramework = CuratorFrameworkFactory.builder() curatorFramework = CuratorFrameworkFactory.builder()

View File

@ -16,24 +16,19 @@
*/ */
package org.apache.solr.security; package org.apache.solr.security;
import java.lang.invoke.MethodHandles;
import java.security.Principal; import java.security.Principal;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* Rule Based Authz plugin implementation which reads user roles from the request. This requires * Rule Based Authz plugin implementation which reads user roles from the request. This requires
* a Principal implementing VerifiedUserRoles interface, e.g. JWTAuthenticationPlugin * a Principal implementing VerifiedUserRoles interface, e.g. JWTAuthenticationPlugin
*/ */
public class ExternalRoleRuleBasedAuthorizationPlugin extends RuleBasedAuthorizationPluginBase { public class ExternalRoleRuleBasedAuthorizationPlugin extends RuleBasedAuthorizationPluginBase {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @Override
@Override
public void init(Map<String, Object> initInfo) { public void init(Map<String, Object> initInfo) {
super.init(initInfo); super.init(initInfo);
if (initInfo.containsKey("user-role")) { if (initInfo.containsKey("user-role")) {

View File

@ -114,9 +114,7 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
putParamOptional(params, "kerberos.keytab", KEYTAB_PARAM); putParamOptional(params, "kerberos.keytab", KEYTAB_PARAM);
} }
String delegationTokenStr = System.getProperty(DELEGATION_TOKEN_ENABLED, null); boolean delegationTokenEnabled = Boolean.getBoolean(DELEGATION_TOKEN_ENABLED);
boolean delegationTokenEnabled =
(delegationTokenStr == null) ? false : Boolean.parseBoolean(delegationTokenStr);
ZkController controller = coreContainer.getZkController(); ZkController controller = coreContainer.getZkController();
if (delegationTokenEnabled) { if (delegationTokenEnabled) {
@ -162,7 +160,7 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
} }
// check impersonator config // check impersonator config
for (@SuppressWarnings({"rawtypes"})Enumeration e = System.getProperties().propertyNames(); e.hasMoreElements();) { for (Enumeration<?> e = System.getProperties().propertyNames(); e.hasMoreElements();) {
String key = e.nextElement().toString(); String key = e.nextElement().toString();
if (key.startsWith(IMPERSONATOR_PREFIX)) { if (key.startsWith(IMPERSONATOR_PREFIX)) {
if (!delegationTokenEnabled) { if (!delegationTokenEnabled) {

View File

@ -16,15 +16,11 @@
*/ */
package org.apache.solr.security; package org.apache.solr.security;
import java.lang.invoke.MethodHandles;
import java.security.Principal; import java.security.Principal;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.solr.handler.admin.SecurityConfHandler.getMapValue; import static org.apache.solr.handler.admin.SecurityConfHandler.getMapValue;
/** /**
@ -32,24 +28,33 @@ import static org.apache.solr.handler.admin.SecurityConfHandler.getMapValue;
* mapping in the security.json configuration * mapping in the security.json configuration
*/ */
public class RuleBasedAuthorizationPlugin extends RuleBasedAuthorizationPluginBase { public class RuleBasedAuthorizationPlugin extends RuleBasedAuthorizationPluginBase {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final Map<String, Set<String>> usersVsRoles = new HashMap<>(); private final Map<String, Set<String>> usersVsRoles = new HashMap<>();
private boolean useShortName;
@Override @Override
public void init(Map<String, Object> initInfo) { public void init(Map<String, Object> initInfo) {
super.init(initInfo); super.init(initInfo);
Map<String, Object> map = getMapValue(initInfo, "user-role"); Map<String, Object> map = getMapValue(initInfo, "user-role");
for (Object o : map.entrySet()) { for (Object o : map.entrySet()) {
@SuppressWarnings({"rawtypes"}) @SuppressWarnings("unchecked")
Map.Entry e = (Map.Entry) o; Map.Entry<String, ?> e = (Map.Entry<String, ?>) o;
String roleName = (String) e.getKey(); String roleName = e.getKey();
usersVsRoles.put(roleName, Permission.readValueAsSet(map, roleName)); usersVsRoles.put(roleName, Permission.readValueAsSet(map, roleName));
} }
useShortName = Boolean.parseBoolean(initInfo.getOrDefault("useShortName", Boolean.FALSE).toString());
}
@Override
public Set<String> getUserRoles(AuthorizationContext context) {
if (useShortName) {
return usersVsRoles.get(context.getUserName());
} else {
return getUserRoles(context.getUserPrincipal());
}
} }
/** /**
* Look up user's role from the explicit user-role mapping * Look up user's role from the explicit user-role mapping.
* *
* @param principal the user Principal from the request * @param principal the user Principal from the request
* @return set of roles as strings * @return set of roles as strings

View File

@ -20,6 +20,7 @@ import java.io.IOException;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.security.Principal; import java.security.Principal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -34,8 +35,6 @@ import org.apache.solr.common.util.CommandOperation;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableMap;
import static java.util.function.Function.identity; import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toMap;
import static org.apache.solr.handler.admin.SecurityConfHandler.getListValue; import static org.apache.solr.handler.admin.SecurityConfHandler.getListValue;
@ -47,38 +46,40 @@ public abstract class RuleBasedAuthorizationPluginBase implements AuthorizationP
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final Map<String, WildCardSupportMap> mapping = new HashMap<>(); private final Map<String, WildCardSupportMap> mapping = new HashMap<>();
private final List<Permission> permissions = new ArrayList<>();
// Doesn't implement Map because we violate the contracts of put() and get()
private static class WildCardSupportMap extends HashMap<String, List<Permission>> { private static class WildCardSupportMap {
final Set<String> wildcardPrefixes = new HashSet<>(); final Set<String> wildcardPrefixes = new HashSet<>();
final Map<String, List<Permission>> delegate = new HashMap<>();
@Override
public List<Permission> put(String key, List<Permission> value) { public List<Permission> put(String key, List<Permission> value) {
if (key != null && key.endsWith("/*")) { if (key != null && key.endsWith("/*")) {
key = key.substring(0, key.length() - 2); key = key.substring(0, key.length() - 2);
wildcardPrefixes.add(key); wildcardPrefixes.add(key);
} }
return super.put(key, value); return delegate.put(key, value);
} }
@Override public List<Permission> get(String key) {
public List<Permission> get(Object key) { List<Permission> result = delegate.get(key);
List<Permission> result = super.get(key);
if (key == null || result != null) return result; if (key == null || result != null) return result;
if (!wildcardPrefixes.isEmpty()) {
for (String s : wildcardPrefixes) { for (String s : wildcardPrefixes) {
if (key.toString().startsWith(s)) { if (key.startsWith(s)) {
List<Permission> l = super.get(s); List<Permission> wildcardPermissions = delegate.get(s);
if (l != null) { if (wildcardPermissions != null) {
result = result == null ? new ArrayList<>() : new ArrayList<>(result); if (result == null) result = new ArrayList<>();
result.addAll(l); result.addAll(wildcardPermissions);
}
} }
} }
} }
return result; return result;
} }
public Set<String> keySet() {
return delegate.keySet();
}
} }
@Override @Override
@ -109,8 +110,7 @@ public abstract class RuleBasedAuthorizationPluginBase implements AuthorizationP
return flag.rsp; return flag.rsp;
} }
private MatchStatus checkCollPerm(Map<String, List<Permission>> pathVsPerms, private MatchStatus checkCollPerm(WildCardSupportMap pathVsPerms, AuthorizationContext context) {
AuthorizationContext context) {
if (pathVsPerms == null) return MatchStatus.NO_PERMISSIONS_FOUND; if (pathVsPerms == null) return MatchStatus.NO_PERMISSIONS_FOUND;
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
@ -131,7 +131,6 @@ public abstract class RuleBasedAuthorizationPluginBase implements AuthorizationP
if (permissions == null || permissions.isEmpty()) { if (permissions == null || permissions.isEmpty()) {
return MatchStatus.NO_PERMISSIONS_FOUND; return MatchStatus.NO_PERMISSIONS_FOUND;
} }
Principal principal = context.getUserPrincipal();
log.trace("Following perms are associated with this collection and path: [{}]", permissions); log.trace("Following perms are associated with this collection and path: [{}]", permissions);
final Permission governingPermission = findFirstGoverningPermission(permissions, context); final Permission governingPermission = findFirstGoverningPermission(permissions, context);
@ -145,7 +144,7 @@ public abstract class RuleBasedAuthorizationPluginBase implements AuthorizationP
log.debug("Found perm [{}] to govern resource [{}]", governingPermission, context.getResource()); log.debug("Found perm [{}] to govern resource [{}]", governingPermission, context.getResource());
} }
return determineIfPermissionPermitsPrincipal(principal, governingPermission); return determineIfPermissionPermitsPrincipal(context, governingPermission);
} }
private Permission findFirstGoverningPermission(List<Permission> permissions, AuthorizationContext context) { private Permission findFirstGoverningPermission(List<Permission> permissions, AuthorizationContext context) {
@ -216,11 +215,12 @@ public abstract class RuleBasedAuthorizationPluginBase implements AuthorizationP
return true; return true;
} }
private MatchStatus determineIfPermissionPermitsPrincipal(Principal principal, Permission governingPermission) { private MatchStatus determineIfPermissionPermitsPrincipal(AuthorizationContext context, Permission governingPermission) {
if (governingPermission.role == null) { if (governingPermission.role == null) {
log.debug("Governing permission [{}] has no role; permitting access", governingPermission); log.debug("Governing permission [{}] has no role; permitting access", governingPermission);
return MatchStatus.PERMITTED; return MatchStatus.PERMITTED;
} }
Principal principal = context.getUserPrincipal();
if (principal == null) { if (principal == null) {
log.debug("Governing permission [{}] has role, but request principal cannot be identified; forbidding access", governingPermission); log.debug("Governing permission [{}] has role, but request principal cannot be identified; forbidding access", governingPermission);
return MatchStatus.USER_REQUIRED; return MatchStatus.USER_REQUIRED;
@ -229,7 +229,7 @@ public abstract class RuleBasedAuthorizationPluginBase implements AuthorizationP
return MatchStatus.PERMITTED; return MatchStatus.PERMITTED;
} }
Set<String> userRoles = getUserRoles(principal); Set<String> userRoles = getUserRoles(context);
for (String role : governingPermission.role) { for (String role : governingPermission.role) {
if (userRoles != null && userRoles.contains(role)) { if (userRoles != null && userRoles.contains(role)) {
log.debug("Governing permission [{}] allows access to role [{}]; permitting access", governingPermission, role); log.debug("Governing permission [{}] allows access to role [{}]; permitting access", governingPermission, role);
@ -260,7 +260,7 @@ public abstract class RuleBasedAuthorizationPluginBase implements AuthorizationP
@Override @Override
@SuppressWarnings({"unchecked"}) @SuppressWarnings({"unchecked"})
public void init(@SuppressWarnings({"rawtypes"})Map<String, Object> initInfo) { public void init(Map<String, Object> initInfo) {
mapping.put(null, new WildCardSupportMap()); mapping.put(null, new WildCardSupportMap());
@SuppressWarnings({"rawtypes"}) @SuppressWarnings({"rawtypes"})
List<Map> perms = getListValue(initInfo, "permissions"); List<Map> perms = getListValue(initInfo, "permissions");
@ -272,7 +272,6 @@ public abstract class RuleBasedAuthorizationPluginBase implements AuthorizationP
log.error("Invalid permission ", exp); log.error("Invalid permission ", exp);
continue; continue;
} }
permissions.add(p);
add2Mapping(p); add2Mapping(p);
} }
} }
@ -280,8 +279,7 @@ public abstract class RuleBasedAuthorizationPluginBase implements AuthorizationP
//this is to do optimized lookup of permissions for a given collection/path //this is to do optimized lookup of permissions for a given collection/path
private void add2Mapping(Permission permission) { private void add2Mapping(Permission permission) {
for (String c : permission.collections) { for (String c : permission.collections) {
WildCardSupportMap m = mapping.get(c); WildCardSupportMap m = mapping.computeIfAbsent(c, k -> new WildCardSupportMap());
if (m == null) mapping.put(c, m = new WildCardSupportMap());
for (String path : permission.path) { for (String path : permission.path) {
List<Permission> perms = m.get(path); List<Permission> perms = m.get(path);
if (perms == null) m.put(path, perms = new ArrayList<>()); if (perms == null) m.put(path, perms = new ArrayList<>());
@ -290,6 +288,15 @@ public abstract class RuleBasedAuthorizationPluginBase implements AuthorizationP
} }
} }
/**
* Finds user roles
* @param context the authorization context to load roles from
* @return set of roles as strings or empty set if no roles are found
*/
public Set<String> getUserRoles(AuthorizationContext context) {
return getUserRoles(context.getUserPrincipal());
}
/** /**
* Finds users roles * Finds users roles
* @param principal the user Principal to fetch roles for * @param principal the user Principal to fetch roles for
@ -330,12 +337,11 @@ public abstract class RuleBasedAuthorizationPluginBase implements AuthorizationP
return latestConf; return latestConf;
} }
private static final Map<String, AutorizationEditOperation> ops = unmodifiableMap(asList(AutorizationEditOperation.values()).stream().collect(toMap(AutorizationEditOperation::getOperationName, identity()))); private static final Map<String, AutorizationEditOperation> ops = Arrays.stream(AutorizationEditOperation.values()).collect(toMap(AutorizationEditOperation::getOperationName, identity()));
@Override @Override
public ValidatingJsonMap getSpec() { public ValidatingJsonMap getSpec() {
return Utils.getSpec("cluster.security.RuleBasedAuthorization").getSpec(); return Utils.getSpec("cluster.security.RuleBasedAuthorization").getSpec();
} }
} }

View File

@ -1133,6 +1133,11 @@ public class HttpSolrCall {
return getReq().getUserPrincipal(); return getReq().getUserPrincipal();
} }
@Override
public String getUserName() {
return getReq().getRemoteUser();
}
@Override @Override
public String getHttpHeader(String s) { public String getHttpHeader(String s) {
return getReq().getHeader(s); return getReq().getHeader(s);

View File

@ -0,0 +1,37 @@
{
"authentication": {
"class": "org.apache.solr.security.ConfigurableInternodeAuthHadoopPlugin",
"sysPropPrefix": "solr.",
"type": "kerberos",
"clientBuilderFactory": "org.apache.solr.client.solrj.impl.Krb5HttpClientBuilder",
"initKerberosZk": "true",
"authConfigs": [
"kerberos.principal",
"kerberos.keytab",
"kerberos.name.rules"
],
"defaultConfigs": {
}
},
"authorization":{
"class":"solr.RuleBasedAuthorizationPlugin",
"useShortName": "true",
"permissions":[
{
"name":"collection-admin-edit",
"role":"admin"
},
{
"name":"read",
"role":"admin"
},
{
"name":"update",
"role":"admin"
}
],
"user-role":{
"solr":"admin"
}
}
}

View File

@ -23,10 +23,8 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
import org.apache.http.auth.BasicUserPrincipal; import org.apache.http.auth.BasicUserPrincipal;
import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.SolrTestCaseJ4;
@ -45,8 +43,10 @@ import org.apache.solr.handler.component.SearchHandler;
import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.security.AuthorizationContext.CollectionRequest; import org.apache.solr.security.AuthorizationContext.CollectionRequest;
import org.apache.solr.security.AuthorizationContext.RequestType; import org.apache.solr.security.AuthorizationContext.RequestType;
import org.apache.solr.util.LogLevel;
import org.hamcrest.core.IsInstanceOf; import org.hamcrest.core.IsInstanceOf;
import org.hamcrest.core.IsNot; import org.hamcrest.core.IsNot;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
@ -54,16 +54,18 @@ import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static org.apache.solr.common.util.CommandOperation.captureErrors; import static org.apache.solr.common.util.CommandOperation.captureErrors;
import static org.apache.solr.common.util.Utils.getObjectByPath; import static org.apache.solr.common.util.Utils.getObjectByPath;
import static org.apache.solr.common.util.Utils.makeMap; import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assume.assumeThat;
/** /**
* Base class for testing RBAC. This will test the {@link RuleBasedAuthorizationPlugin} implementation * Base class for testing RBAC. This will test the {@link RuleBasedAuthorizationPlugin} implementation
* but also serves as a base class for testing other sub classes * but also serves as a base class for testing other sub classes
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@LogLevel("org.apache.solr.security=TRACE")
public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 { public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
@SuppressWarnings({"rawtypes"}) protected Map<String, Object> rules;
protected Map rules;
final int STATUS_OK = 200; final int STATUS_OK = 200;
final int FORBIDDEN = 403; final int FORBIDDEN = 403;
@ -75,6 +77,11 @@ public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
resetPermissionsAndRoles(); resetPermissionsAndRoles();
} }
@Before
public void setupPermissionsAndRoles() {
resetPermissionsAndRoles();
}
protected void resetPermissionsAndRoles() { protected void resetPermissionsAndRoles() {
String permissions = "{" + String permissions = "{" +
" user-role : {" + " user-role : {" +
@ -97,110 +104,101 @@ public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
" }," + " }," +
"{name:read, role:dev }," + "{name:read, role:dev }," +
"{name:freeforall, path:'/foo', role:'*'}]}"; "{name:freeforall, path:'/foo', role:'*'}]}";
rules = (Map) Utils.fromJSONString(permissions); rules = (Map<String,Object>) Utils.fromJSONString(permissions);
} }
@Test @Test
public void testBasicPermissions() { public void testBasicPermissions() {
checkRules(makeMap("resource", "/update/json/docs", checkRules(Map.of("resource", "/update/json/docs",
"httpMethod", "POST", "httpMethod", "POST",
"userPrincipal", "unknownuser", "userPrincipal", "unknownuser",
"collectionRequests", "freeforall", "collectionRequests", "freeforall",
"handler", new UpdateRequestHandler()) "handler", new UpdateRequestHandler())
, STATUS_OK); , STATUS_OK);
checkRules(makeMap("resource", "/update/json/docs", checkRules(Map.of("resource", "/update/json/docs",
"httpMethod", "POST", "httpMethod", "POST",
"userPrincipal", "tim", "userPrincipal", "tim",
"collectionRequests", "mycoll", "collectionRequests", "mycoll",
"handler", new UpdateRequestHandler()) "handler", new UpdateRequestHandler())
, STATUS_OK); , STATUS_OK);
checkRules(makeMap("resource", "/update/json/docs", checkRules(Map.of("resource", "/update/json/docs",
"httpMethod", "POST", "httpMethod", "POST",
"collectionRequests", "mycoll", "collectionRequests", "mycoll",
"handler", new UpdateRequestHandler()) "handler", new UpdateRequestHandler())
, PROMPT_FOR_CREDENTIALS); , PROMPT_FOR_CREDENTIALS);
checkRules(makeMap("resource", "/schema", checkRules(Map.of("resource", "/schema",
"userPrincipal", "somebody", "userPrincipal", "somebody",
"collectionRequests", "mycoll", "collectionRequests", "mycoll",
"httpMethod", "POST", "httpMethod", "POST",
"handler", new SchemaHandler()) "handler", new SchemaHandler())
, FORBIDDEN); , FORBIDDEN);
checkRules(makeMap("resource", "/schema", checkRules(Map.of("resource", "/schema",
"userPrincipal", "somebody", "userPrincipal", "somebody",
"collectionRequests", "mycoll", "collectionRequests", "mycoll",
"httpMethod", "GET", "httpMethod", "GET",
"handler", new SchemaHandler()) "handler", new SchemaHandler())
, STATUS_OK); , STATUS_OK);
checkRules(makeMap("resource", "/schema/fields", checkRules(Map.of("resource", "/schema/fields",
"userPrincipal", "somebody", "userPrincipal", "somebody",
"collectionRequests", "mycoll", "collectionRequests", "mycoll",
"httpMethod", "GET", "httpMethod", "GET",
"handler", new SchemaHandler()) "handler", new SchemaHandler())
, STATUS_OK); , STATUS_OK);
checkRules(makeMap("resource", "/schema", checkRules(Map.of("resource", "/schema",
"userPrincipal", "somebody", "userPrincipal", "somebody",
"collectionRequests", "mycoll", "collectionRequests", "mycoll",
"httpMethod", "POST", "httpMethod", "POST",
"handler", new SchemaHandler()) "handler", new SchemaHandler())
, FORBIDDEN); , FORBIDDEN);
checkRules(makeMap("resource", "/admin/collections", checkRules(Map.of("resource", "/admin/collections",
"userPrincipal", "tim", "userPrincipal", "tim",
"requestType", RequestType.ADMIN, "requestType", RequestType.ADMIN,
"collectionRequests", null,
"httpMethod", "GET", "httpMethod", "GET",
"handler", new CollectionsHandler(), "handler", new CollectionsHandler(),
"params", new MapSolrParams(singletonMap("action", "LIST"))) "params", new MapSolrParams(singletonMap("action", "LIST")))
, STATUS_OK); , STATUS_OK);
checkRules(makeMap("resource", "/admin/collections", checkRules(Map.of("resource", "/admin/collections",
"userPrincipal", null,
"requestType", RequestType.ADMIN, "requestType", RequestType.ADMIN,
"collectionRequests", null,
"httpMethod", "GET", "httpMethod", "GET",
"handler", new CollectionsHandler(), "handler", new CollectionsHandler(),
"params", new MapSolrParams(singletonMap("action", "LIST"))) "params", new MapSolrParams(singletonMap("action", "LIST")))
, STATUS_OK); , STATUS_OK);
checkRules(makeMap("resource", "/admin/collections", checkRules(Map.of("resource", "/admin/collections",
"userPrincipal", null,
"requestType", RequestType.ADMIN, "requestType", RequestType.ADMIN,
"collectionRequests", null,
"handler", new CollectionsHandler(), "handler", new CollectionsHandler(),
"params", new MapSolrParams(singletonMap("action", "CREATE"))) "params", new MapSolrParams(singletonMap("action", "CREATE")))
, PROMPT_FOR_CREDENTIALS); , PROMPT_FOR_CREDENTIALS);
checkRules(makeMap("resource", "/admin/collections", checkRules(Map.of("resource", "/admin/collections",
"userPrincipal", null,
"requestType", RequestType.ADMIN, "requestType", RequestType.ADMIN,
"collectionRequests", null,
"handler", new CollectionsHandler(), "handler", new CollectionsHandler(),
"params", new MapSolrParams(singletonMap("action", "RELOAD"))) "params", new MapSolrParams(singletonMap("action", "RELOAD")))
, PROMPT_FOR_CREDENTIALS); , PROMPT_FOR_CREDENTIALS);
checkRules(makeMap("resource", "/admin/collections", checkRules(Map.of("resource", "/admin/collections",
"userPrincipal", "somebody", "userPrincipal", "somebody",
"requestType", RequestType.ADMIN, "requestType", RequestType.ADMIN,
"collectionRequests", null,
"handler", new CollectionsHandler(), "handler", new CollectionsHandler(),
"params", new MapSolrParams(singletonMap("action", "CREATE"))) "params", new MapSolrParams(singletonMap("action", "CREATE")))
, FORBIDDEN); , FORBIDDEN);
checkRules(makeMap("resource", "/admin/collections", checkRules(Map.of("resource", "/admin/collections",
"userPrincipal", "tim", "userPrincipal", "tim",
"requestType", RequestType.ADMIN, "requestType", RequestType.ADMIN,
"collectionRequests", null,
"handler", new CollectionsHandler(), "handler", new CollectionsHandler(),
"params", new MapSolrParams(singletonMap("action", "CREATE"))) "params", new MapSolrParams(singletonMap("action", "CREATE")))
, STATUS_OK); , STATUS_OK);
checkRules(makeMap("resource", "/select", checkRules(Map.of("resource", "/select",
"httpMethod", "GET", "httpMethod", "GET",
"handler", new SearchHandler(), "handler", new SearchHandler(),
"collectionRequests", singletonList(new CollectionRequest("mycoll")), "collectionRequests", singletonList(new CollectionRequest("mycoll")),
@ -210,94 +208,89 @@ public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
setUserRole("cio", "su"); setUserRole("cio", "su");
addPermission("all", "su"); addPermission("all", "su");
checkRules(makeMap("resource", ReplicationHandler.PATH, checkRules(Map.of("resource", ReplicationHandler.PATH,
"httpMethod", "POST", "httpMethod", "POST",
"userPrincipal", "tim", "userPrincipal", "tim",
"handler", new ReplicationHandler(), "handler", new ReplicationHandler(),
"collectionRequests", singletonList(new CollectionRequest("mycoll")) ) "collectionRequests", singletonList(new CollectionRequest("mycoll")) )
, FORBIDDEN); , FORBIDDEN);
checkRules(makeMap("resource", ReplicationHandler.PATH, checkRules(Map.of("resource", ReplicationHandler.PATH,
"httpMethod", "POST", "httpMethod", "POST",
"userPrincipal", "cio", "userPrincipal", "cio",
"handler", new ReplicationHandler(), "handler", new ReplicationHandler(),
"collectionRequests", singletonList(new CollectionRequest("mycoll")) ) "collectionRequests", singletonList(new CollectionRequest("mycoll")) )
, STATUS_OK); , STATUS_OK);
checkRules(makeMap("resource", "/admin/collections", checkRules(Map.of("resource", "/admin/collections",
"userPrincipal", "tim", "userPrincipal", "tim",
"requestType", AuthorizationContext.RequestType.ADMIN, "requestType", AuthorizationContext.RequestType.ADMIN,
"collectionRequests", null,
"handler", new CollectionsHandler(), "handler", new CollectionsHandler(),
"params", new MapSolrParams(singletonMap("action", "CREATE"))) "params", new MapSolrParams(singletonMap("action", "CREATE")))
, STATUS_OK); , STATUS_OK);
}
resetPermissionsAndRoles(); @Test
public void testCoreAdminPermissions() {
addPermission("core-admin-edit", "su"); addPermission("core-admin-edit", "su");
addPermission("core-admin-read", "user"); addPermission("core-admin-read", "user");
setUserRole("cio", "su"); setUserRole("cio", "su");
addPermission("all", "su"); addPermission("all", "su");
checkRules(makeMap("resource", "/admin/cores", checkRules(Map.of("resource", "/admin/cores",
"userPrincipal", null,
"requestType", RequestType.ADMIN, "requestType", RequestType.ADMIN,
"collectionRequests", null,
"handler", new CoreAdminHandler(null), "handler", new CoreAdminHandler(null),
"params", new MapSolrParams(singletonMap("action", "CREATE"))) "params", new MapSolrParams(singletonMap("action", "CREATE")))
, PROMPT_FOR_CREDENTIALS); , PROMPT_FOR_CREDENTIALS);
checkRules(makeMap("resource", "/admin/cores", checkRules(Map.of("resource", "/admin/cores",
"userPrincipal", "joe", "userPrincipal", "joe",
"requestType", RequestType.ADMIN, "requestType", RequestType.ADMIN,
"collectionRequests", null,
"handler", new CoreAdminHandler(null), "handler", new CoreAdminHandler(null),
"params", new MapSolrParams(singletonMap("action", "CREATE"))) "params", new MapSolrParams(singletonMap("action", "CREATE")))
, FORBIDDEN); , FORBIDDEN);
checkRules(makeMap("resource", "/admin/cores", checkRules(Map.of("resource", "/admin/cores",
"userPrincipal", "joe", "userPrincipal", "joe",
"requestType", RequestType.ADMIN, "requestType", RequestType.ADMIN,
"collectionRequests", null,
"handler", new CoreAdminHandler(null), "handler", new CoreAdminHandler(null),
"params", new MapSolrParams(singletonMap("action", "STATUS"))) "params", new MapSolrParams(singletonMap("action", "STATUS")))
, STATUS_OK); , STATUS_OK);
checkRules(makeMap("resource", "/admin/cores", checkRules(Map.of("resource", "/admin/cores",
"userPrincipal", "cio", "userPrincipal", "cio",
"requestType", RequestType.ADMIN, "requestType", RequestType.ADMIN,
"collectionRequests", null,
"handler", new CoreAdminHandler(null), "handler", new CoreAdminHandler(null),
"params", new MapSolrParams(singletonMap("action", "CREATE"))) "params", new MapSolrParams(singletonMap("action", "CREATE")))
,STATUS_OK); ,STATUS_OK);
}
resetPermissionsAndRoles(); @Test
addPermission("test-params", "admin", "/x", makeMap("key", Arrays.asList("REGEX:(?i)val1", "VAL2"))); public void testParamsPermissions() {
addPermission("test-params", "admin", "/x", Map.of("key", Arrays.asList("REGEX:(?i)val1", "VAL2")));
checkRules(makeMap("resource", "/x", checkRules(Map.of("resource", "/x",
"userPrincipal", null,
"requestType", RequestType.UNKNOWN, "requestType", RequestType.UNKNOWN,
"collectionRequests", "go", "collectionRequests", "go",
"handler", new DumpRequestHandler(), "handler", new DumpRequestHandler(),
"params", new MapSolrParams(singletonMap("key", "VAL1"))) "params", new MapSolrParams(singletonMap("key", "VAL1")))
, PROMPT_FOR_CREDENTIALS); , PROMPT_FOR_CREDENTIALS);
checkRules(makeMap("resource", "/x", checkRules(Map.of("resource", "/x",
"userPrincipal", null,
"requestType", RequestType.UNKNOWN, "requestType", RequestType.UNKNOWN,
"collectionRequests", "go", "collectionRequests", "go",
"handler", new DumpRequestHandler(), "handler", new DumpRequestHandler(),
"params", new MapSolrParams(singletonMap("key", "Val1"))) "params", new MapSolrParams(singletonMap("key", "Val1")))
, PROMPT_FOR_CREDENTIALS); , PROMPT_FOR_CREDENTIALS);
checkRules(makeMap("resource", "/x", checkRules(Map.of("resource", "/x",
"userPrincipal", null,
"requestType", RequestType.UNKNOWN, "requestType", RequestType.UNKNOWN,
"collectionRequests", "go", "collectionRequests", "go",
"handler", new DumpRequestHandler(), "handler", new DumpRequestHandler(),
"params", new MapSolrParams(singletonMap("key", "Val1"))) "params", new MapSolrParams(singletonMap("key", "Val1")))
, PROMPT_FOR_CREDENTIALS); , PROMPT_FOR_CREDENTIALS);
checkRules(makeMap("resource", "/x", checkRules(Map.of("resource", "/x",
"userPrincipal", "joe", "userPrincipal", "joe",
"requestType", RequestType.UNKNOWN, "requestType", RequestType.UNKNOWN,
"collectionRequests", "go", "collectionRequests", "go",
@ -305,7 +298,7 @@ public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
"params", new MapSolrParams(singletonMap("key", "Val1"))) "params", new MapSolrParams(singletonMap("key", "Val1")))
, FORBIDDEN); , FORBIDDEN);
checkRules(makeMap("resource", "/x", checkRules(Map.of("resource", "/x",
"userPrincipal", "joe", "userPrincipal", "joe",
"requestType", RequestType.UNKNOWN, "requestType", RequestType.UNKNOWN,
"collectionRequests", "go", "collectionRequests", "go",
@ -313,26 +306,24 @@ public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
"params", new MapSolrParams(singletonMap("key", "Val2"))) "params", new MapSolrParams(singletonMap("key", "Val2")))
, STATUS_OK); , STATUS_OK);
checkRules(makeMap("resource", "/x", checkRules(Map.of("resource", "/x",
"userPrincipal", "joe", "userPrincipal", "joe",
"requestType", RequestType.UNKNOWN, "requestType", RequestType.UNKNOWN,
"collectionRequests", "go", "collectionRequests", "go",
"handler", new DumpRequestHandler(), "handler", new DumpRequestHandler(),
"params", new MapSolrParams(singletonMap("key", "VAL2"))) "params", new MapSolrParams(singletonMap("key", "VAL2")))
, FORBIDDEN); , FORBIDDEN);
}
@Test
public void testCustomRules() {
Map<String, Object> customRules = (Map<String, Object>) Utils.fromJSONString( Map<String, Object> customRules = (Map<String, Object>) Utils.fromJSONString(
"{permissions:[" + "{permissions:[" +
" {name:update, role:[admin_role,update_role]}," + " {name:update, role:[admin_role,update_role]}," +
" {name:read, role:[admin_role,update_role,read_role]}" + " {name:read, role:[admin_role,update_role,read_role]}" +
"]}"); "]}");
clearUserRoles(); checkRules(Map.of("resource", "/update",
setUserRole("admin", "admin_role");
setUserRole("update", "update_role");
setUserRole("solr", "read_role");
checkRules(makeMap("resource", "/update",
"userPrincipal", "solr", "userPrincipal", "solr",
"requestType", RequestType.UNKNOWN, "requestType", RequestType.UNKNOWN,
"collectionRequests", "go", "collectionRequests", "go",
@ -353,7 +344,7 @@ public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
setUserRole("dev", "dev"); setUserRole("dev", "dev");
setUserRole("admin", "admin"); setUserRole("admin", "admin");
addPermission("all", "dev", "admin"); addPermission("all", "dev", "admin");
checkRules(makeMap("resource", "/update", checkRules(Map.of("resource", "/update",
"userPrincipal", "dev", "userPrincipal", "dev",
"requestType", RequestType.UNKNOWN, "requestType", RequestType.UNKNOWN,
"collectionRequests", "go", "collectionRequests", "go",
@ -363,7 +354,7 @@ public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
handler = new PropertiesRequestHandler(); handler = new PropertiesRequestHandler();
assertThat(handler, new IsNot<>(new IsInstanceOf(PermissionNameProvider.class))); assertThat(handler, new IsNot<>(new IsInstanceOf(PermissionNameProvider.class)));
checkRules(makeMap("resource", "/admin/info/properties", checkRules(Map.of("resource", "/admin/info/properties",
"userPrincipal", "dev", "userPrincipal", "dev",
"requestType", RequestType.UNKNOWN, "requestType", RequestType.UNKNOWN,
"collectionRequests", "go", "collectionRequests", "go",
@ -385,7 +376,7 @@ public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
setUserRole("dev", "dev"); setUserRole("dev", "dev");
setUserRole("admin", "admin"); setUserRole("admin", "admin");
addPermission("all", "*"); addPermission("all", "*");
checkRules(makeMap("resource", "/update", checkRules(Map.of("resource", "/update",
"userPrincipal", "dev", "userPrincipal", "dev",
"requestType", RequestType.UNKNOWN, "requestType", RequestType.UNKNOWN,
"collectionRequests", "go", "collectionRequests", "go",
@ -395,7 +386,7 @@ public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
handler = new PropertiesRequestHandler(); handler = new PropertiesRequestHandler();
assertThat(handler, new IsNot<>(new IsInstanceOf(PermissionNameProvider.class))); assertThat(handler, new IsNot<>(new IsInstanceOf(PermissionNameProvider.class)));
checkRules(makeMap("resource", "/admin/info/properties", checkRules(Map.of("resource", "/admin/info/properties",
"userPrincipal", "dev", "userPrincipal", "dev",
"requestType", RequestType.UNKNOWN, "requestType", RequestType.UNKNOWN,
"collectionRequests", "go", "collectionRequests", "go",
@ -416,7 +407,7 @@ public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
setUserRole("dev", "dev"); setUserRole("dev", "dev");
setUserRole("admin", "admin"); setUserRole("admin", "admin");
addPermission("all", "admin"); addPermission("all", "admin");
checkRules(makeMap("resource", "/update", checkRules(Map.of("resource", "/update",
"userPrincipal", "dev", "userPrincipal", "dev",
"requestType", RequestType.UNKNOWN, "requestType", RequestType.UNKNOWN,
"collectionRequests", "go", "collectionRequests", "go",
@ -426,7 +417,7 @@ public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
handler = new PropertiesRequestHandler(); handler = new PropertiesRequestHandler();
assertThat(handler, new IsNot<>(new IsInstanceOf(PermissionNameProvider.class))); assertThat(handler, new IsNot<>(new IsInstanceOf(PermissionNameProvider.class)));
checkRules(makeMap("resource", "/admin/info/properties", checkRules(Map.of("resource", "/admin/info/properties",
"userPrincipal", "dev", "userPrincipal", "dev",
"requestType", RequestType.UNKNOWN, "requestType", RequestType.UNKNOWN,
"collectionRequests", "go", "collectionRequests", "go",
@ -435,22 +426,36 @@ public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
, FORBIDDEN); , FORBIDDEN);
} }
void addPermission(String permissionName, String role, String path, Map<String, Object> params) { @Test
((List)rules.get("permissions")).add( makeMap("name", permissionName, "role", role, "path", path, "params", params)); public void testShortNameResolvesPermissions() {
assumeThat("ExternalRBAPlugin doesn't use short name",
createPlugin(), is(instanceOf(RuleBasedAuthorizationPlugin.class)));
setUserRole("admin", "admin");
addPermission("all", "admin");
Map<String, Object> values = Map.of(
"userPrincipal", "admin@EXAMPLE",
"userName", "admin",
"resource", "/admin/info/properties",
"requestType", RequestType.ADMIN,
"handler", new PropertiesRequestHandler());
// Short names disabled, admin should fail, admin@EXAMPLE should succeed
rules.put("useShortName", "false");
checkRules(values, FORBIDDEN);
// Short names enabled, admin should succeed, admin@EXAMPLE should fail
rules.put("useShortName", "true");
checkRules(values, STATUS_OK);
} }
void removePermission(String name) { void addPermission(String permissionName, String role, String path, Map<String, Object> params) {
List<Map<String,Object>> oldPerm = ((List) rules.get("permissions")); ((List)rules.get("permissions")).add(Map.of("name", permissionName, "role", role, "path", path, "params", params));
List<Map<String, Object>> newPerm = oldPerm.stream().filter(p -> !p.get("name").equals(name)).collect(Collectors.toList());
rules.put("permissions", newPerm);
} }
protected void addPermission(String permissionName, String... roles) { protected void addPermission(String permissionName, String... roles) {
((List)rules.get("permissions")).add( makeMap("name", permissionName, "role", Arrays.asList(roles))); ((List)rules.get("permissions")).add(Map.of("name", permissionName, "role", Arrays.asList(roles)));
}
void clearUserRoles() {
rules.put("user-role", new HashMap<String,Object>());
} }
protected void setUserRole(String user, String role) { protected void setUserRole(String user, String role) {
@ -465,8 +470,7 @@ public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
assertEquals("admin", perms.getVal("permissions[0]/role")); assertEquals("admin", perms.getVal("permissions[0]/role"));
perms.runCmd("{set-permission : {name: config-edit, role: [admin, dev], index:2 } }", false); perms.runCmd("{set-permission : {name: config-edit, role: [admin, dev], index:2 } }", false);
perms.runCmd("{set-permission : {name: config-edit, role: [admin, dev], index:1}}", true); perms.runCmd("{set-permission : {name: config-edit, role: [admin, dev], index:1}}", true);
@SuppressWarnings({"rawtypes"}) Collection<String> roles = (Collection<String>) perms.getVal("permissions[0]/role");
Collection roles = (Collection) perms.getVal("permissions[0]/role");
assertEquals(2, roles.size()); assertEquals(2, roles.size());
assertTrue(roles.contains("admin")); assertTrue(roles.contains("admin"));
assertTrue(roles.contains("dev")); assertTrue(roles.contains("dev"));
@ -485,16 +489,13 @@ public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
assertEquals("y", perms.getVal("permissions[1]/collection")); assertEquals("y", perms.getVal("permissions[1]/collection"));
} }
static class Perms { static class Perms {
@SuppressWarnings({"rawtypes"}) Map<String, Object> conf = new HashMap<>();
Map conf = new HashMap<>();
ConfigEditablePlugin plugin = new RuleBasedAuthorizationPlugin(); ConfigEditablePlugin plugin = new RuleBasedAuthorizationPlugin();
List<CommandOperation> parsedCommands; List<CommandOperation> parsedCommands;
public void runCmd(String cmds, boolean failOnError) throws IOException { public void runCmd(String cmds, boolean failOnError) throws IOException {
parsedCommands = CommandOperation.parse(new StringReader(cmds)); parsedCommands = CommandOperation.parse(new StringReader(cmds));
@SuppressWarnings({"rawtypes"})
LinkedList ll = new LinkedList();
Map<String, Object> edited = plugin.edit(conf, parsedCommands); Map<String, Object> edited = plugin.edit(conf, parsedCommands);
if(edited!= null) conf = edited; if(edited!= null) conf = edited;
@SuppressWarnings({"rawtypes"}) @SuppressWarnings({"rawtypes"})
@ -536,10 +537,15 @@ public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
Object userPrincipal = values.get("userPrincipal"); Object userPrincipal = values.get("userPrincipal");
return userPrincipal == null ? null : new BasicUserPrincipal(String.valueOf(userPrincipal)); return userPrincipal == null ? null : new BasicUserPrincipal(String.valueOf(userPrincipal));
} }
@Override
public String getUserName() {
return String.valueOf(values.get("userName"));
}
}; };
} }
protected abstract class MockAuthorizationContext extends AuthorizationContext { protected abstract static class MockAuthorizationContext extends AuthorizationContext {
private final Map<String,Object> values; private final Map<String,Object> values;
public MockAuthorizationContext(Map<String, Object> values) { public MockAuthorizationContext(Map<String, Object> values) {
@ -552,14 +558,19 @@ public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
return params == null ? new MapSolrParams(new HashMap<>()) : params; return params == null ? new MapSolrParams(new HashMap<>()) : params;
} }
@Override
public String getUserName() {
Principal userPrincipal = getUserPrincipal();
return userPrincipal == null ? null : userPrincipal.getName();
}
@Override @Override
public String getHttpHeader(String header) { public String getHttpHeader(String header) {
return null; return null;
} }
@Override @Override
@SuppressWarnings({"rawtypes"}) public Enumeration<String> getHeaderNames() {
public Enumeration getHeaderNames() {
return null; return null;
} }

View File

@ -0,0 +1,93 @@
/*
* 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.solr.security.hadoop;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.solr.cloud.KerberosTestServices;
/**
* A utility class which provides common functionality required to test kerberos integration.
*/
public class KerberosUtils {
/**
* This method sets up Hadoop mini-kdc along with relevant Kerberos configuration files
* (e.g. jaas.conf) as well as system properties.
*
* @param baseDir The directory path which should be used by the Hadoop mini-kdc
* @return An instance of {@link KerberosTestServices}
* @throws Exception in case of errors.
*/
static KerberosTestServices setupMiniKdc(Path baseDir) throws Exception {
System.setProperty("solr.jaas.debug", "true");
Path kdcDir = baseDir.resolve("minikdc");
String solrClientPrincipal = "solr";
File keytabFile = kdcDir.resolve("keytabs").toFile();
KerberosTestServices tmp = KerberosTestServices.builder()
.withKdc(kdcDir.toFile())
.withJaasConfiguration(solrClientPrincipal, keytabFile, "SolrClient")
.build();
String solrServerPrincipal = "HTTP/127.0.0.1";
tmp.start();
tmp.getKdc().createPrincipal(keytabFile, solrServerPrincipal, solrClientPrincipal);
String appName = "SolrClient";
String jaas = appName + " {\n"
+ " com.sun.security.auth.module.Krb5LoginModule required\n"
+ " useKeyTab=true\n"
+ " keyTab=\"" + keytabFile.getAbsolutePath() + "\"\n"
+ " storeKey=true\n"
+ " useTicketCache=false\n"
+ " doNotPrompt=true\n"
+ " debug=true\n"
+ " principal=\"" + solrClientPrincipal + "\";\n"
+ "};";
Path jaasFile = kdcDir.resolve("jaas-client.conf");
Files.writeString(jaasFile, jaas);
System.setProperty("java.security.auth.login.config", jaasFile.toString());
System.setProperty("solr.kerberos.jaas.appname", appName);
System.setProperty("solr.kerberos.principal", solrServerPrincipal);
System.setProperty("solr.kerberos.keytab", keytabFile.getAbsolutePath());
// Extracts 127.0.0.1 from HTTP/127.0.0.1@EXAMPLE.COM
System.setProperty("solr.kerberos.name.rules", "RULE:[1:$1@$0](.*EXAMPLE.COM)s/@.*//"
+ "\nRULE:[2:$2@$0](.*EXAMPLE.COM)s/@.*//"
+ "\nDEFAULT"
);
return tmp;
}
/**
* This method stops the Hadoop mini-kdc instance as well as cleanup relevant Java system properties.
*
* @param kerberosTestServices An instance of Hadoop mini-kdc
*/
public static void cleanupMiniKdc(KerberosTestServices kerberosTestServices) {
System.clearProperty("java.security.auth.login.config");
System.clearProperty("solr.kerberos.principal");
System.clearProperty("solr.kerberos.keytab");
System.clearProperty("solr.kerberos.name.rules");
System.clearProperty("solr.jaas.debug");
if (kerberosTestServices != null) {
kerberosTestServices.stop();
}
}
}

View File

@ -0,0 +1,81 @@
/*
* 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.solr.security.hadoop;
import org.apache.lucene.util.Constants;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.cloud.AbstractDistribZkTestBase;
import org.apache.solr.cloud.KerberosTestServices;
import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.common.SolrInputDocument;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class TestRuleBasedAuthorizationWithKerberos extends SolrCloudTestCase {
protected static final int NUM_SERVERS = 1;
protected static final int NUM_SHARDS = 1;
protected static final int REPLICATION_FACTOR = 1;
private static KerberosTestServices kerberosTestServices;
@BeforeClass
public static void setupClass() throws Exception {
assumeFalse("Hadoop does not work on Windows", Constants.WINDOWS);
kerberosTestServices = KerberosUtils.setupMiniKdc(createTempDir());
configureCluster(NUM_SERVERS)// nodes
.withSecurityJson(TEST_PATH().resolve("security").resolve("hadoop_kerberos_authz_config.json"))
.addConfig("conf1", TEST_PATH().resolve("configsets").resolve("cloud-minimal").resolve("conf"))
.configure();
}
@AfterClass
public static void tearDownClass() {
KerberosUtils.cleanupMiniKdc(kerberosTestServices);
kerberosTestServices = null;
}
@Test
public void testCollectionCreateSearchDelete() throws Exception {
CloudSolrClient solrClient = cluster.getSolrClient();
String collectionName = "testkerberoscollection_authz";
// create collection
CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName, "conf1",
NUM_SHARDS, REPLICATION_FACTOR);
create.process(solrClient);
SolrInputDocument doc = new SolrInputDocument();
doc.setField("id", "1");
solrClient.add(collectionName, doc);
solrClient.commit(collectionName);
SolrQuery query = new SolrQuery();
query.setQuery("*:*");
QueryResponse rsp = solrClient.query(collectionName, query);
assertEquals(1, rsp.getResults().getNumFound());
CollectionAdminRequest.Delete deleteReq = CollectionAdminRequest.deleteCollection(collectionName);
deleteReq.process(solrClient);
AbstractDistribZkTestBase.waitForCollectionToDisappear(collectionName,
solrClient.getZkStateReader(), true, 330);
}
}

View File

@ -16,10 +16,6 @@
*/ */
package org.apache.solr.security.hadoop; package org.apache.solr.security.hadoop;
import java.io.File;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.FileUtils;
import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.impl.CloudSolrClient; import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest; import org.apache.solr.client.solrj.request.CollectionAdminRequest;
@ -43,7 +39,7 @@ public class TestSolrCloudWithHadoopAuthPlugin extends SolrCloudAuthTestCase {
public static void setupClass() throws Exception { public static void setupClass() throws Exception {
HdfsTestUtil.checkAssumptions(); HdfsTestUtil.checkAssumptions();
setupMiniKdc(); kerberosTestServices = KerberosUtils.setupMiniKdc(createTempDir());
configureCluster(NUM_SERVERS)// nodes configureCluster(NUM_SERVERS)// nodes
.withSecurityJson(TEST_PATH().resolve("security").resolve("hadoop_kerberos_config.json")) .withSecurityJson(TEST_PATH().resolve("security").resolve("hadoop_kerberos_config.json"))
@ -54,55 +50,10 @@ public class TestSolrCloudWithHadoopAuthPlugin extends SolrCloudAuthTestCase {
@AfterClass @AfterClass
public static void tearDownClass() throws Exception { public static void tearDownClass() throws Exception {
System.clearProperty("java.security.auth.login.config"); KerberosUtils.cleanupMiniKdc(kerberosTestServices);
System.clearProperty("solr.kerberos.principal");
System.clearProperty("solr.kerberos.keytab");
System.clearProperty("solr.kerberos.name.rules");
System.clearProperty("solr.jaas.debug");
if (kerberosTestServices != null) {
kerberosTestServices.stop();
}
kerberosTestServices = null; kerberosTestServices = null;
} }
private static void setupMiniKdc() throws Exception {
System.setProperty("solr.jaas.debug", "true");
String kdcDir = createTempDir()+File.separator+"minikdc";
String solrClientPrincipal = "solr";
File keytabFile = new File(kdcDir, "keytabs");
kerberosTestServices = KerberosTestServices.builder()
.withKdc(new File(kdcDir))
.withJaasConfiguration(solrClientPrincipal, keytabFile, "SolrClient")
.build();
String solrServerPrincipal = "HTTP/127.0.0.1";
kerberosTestServices.start();
kerberosTestServices.getKdc().createPrincipal(keytabFile, solrServerPrincipal, solrClientPrincipal);
String jaas = "SolrClient {\n"
+ " com.sun.security.auth.module.Krb5LoginModule required\n"
+ " useKeyTab=true\n"
+ " keyTab=\"" + keytabFile.getAbsolutePath() + "\"\n"
+ " storeKey=true\n"
+ " useTicketCache=false\n"
+ " doNotPrompt=true\n"
+ " debug=true\n"
+ " principal=\"" + solrClientPrincipal + "\";\n"
+ "};";
String jaasFilePath = kdcDir+File.separator+"jaas-client.conf";
FileUtils.write(new File(jaasFilePath), jaas, StandardCharsets.UTF_8);
System.setProperty("java.security.auth.login.config", jaasFilePath);
System.setProperty("solr.kerberos.jaas.appname", "SolrClient"); // Get this app name from the jaas file
System.setProperty("solr.kerberos.principal", solrServerPrincipal);
System.setProperty("solr.kerberos.keytab", keytabFile.getAbsolutePath());
// Extracts 127.0.0.1 from HTTP/127.0.0.1@EXAMPLE.COM
System.setProperty("solr.kerberos.name.rules", "RULE:[1:$1@$0](.*EXAMPLE.COM)s/@.*//"
+ "\nRULE:[2:$2@$0](.*EXAMPLE.COM)s/@.*//"
+ "\nDEFAULT"
);
}
@Test @Test
public void testBasics() throws Exception { public void testBasics() throws Exception {
testCollectionCreateSearchDelete(); testCollectionCreateSearchDelete();

View File

@ -74,7 +74,6 @@ The syntax for individual permissions is more involved and is treated in greater
User's roles may either come from the request itself, then you will use the `ExternalRoleRuleBasedAuthorizationPlugin` variant of RBAC. If you need to hardcode user-role mappings, then you need to use the `RuleBasedAuthorizationPlugin` and define the user-role mappings in `security.json` like this: User's roles may either come from the request itself, then you will use the `ExternalRoleRuleBasedAuthorizationPlugin` variant of RBAC. If you need to hardcode user-role mappings, then you need to use the `RuleBasedAuthorizationPlugin` and define the user-role mappings in `security.json` like this:
user-role:: A mapping of individual users to the roles they belong to. The value of this property is a JSON map, where each property name is a user, and each property value is either the name of a single role or a JSON array of multiple roles that the specified user belongs to. For example: user-role:: A mapping of individual users to the roles they belong to. The value of this property is a JSON map, where each property name is a user, and each property value is either the name of a single role or a JSON array of multiple roles that the specified user belongs to. For example:
+
[source,json] [source,json]
---- ----
"user-role": { "user-role": {
@ -82,6 +81,11 @@ user-role:: A mapping of individual users to the roles they belong to. The valu
"user2": ["role1", "role2"] "user2": ["role1", "role2"]
} }
---- ----
useShortName:: Determines if user-role mappings will resolve using the full principal or a shortened name provided by the authentication plugin. For example, the `KerberosAuthPlugin` may provide a full principal as `user@EXAMPLE.COM`, while the corresponding short name would be `user`.
+
For some plugins the principal name and short name may be the same.
+
This setting is optional and defaults to `false` if unset.
=== Example for RuleBasedAuthorizationPlugin and BasicAuth === Example for RuleBasedAuthorizationPlugin and BasicAuth