Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

Expand All @@ -69,6 +70,8 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;

import static org.apache.ranger.security.web.filter.RangerHeaderPreAuthFilter.PROP_HEADER_AUTH_ENABLED;

@Component
@Transactional
public class SessionMgr {
Expand Down Expand Up @@ -97,10 +100,19 @@ public class SessionMgr {
@Autowired
StringUtil stringUtil;

boolean ssoEnabled;
boolean headerAuthEnabled;

public SessionMgr() {
logger.debug("SessionManager created");
}

@PostConstruct
public void init() {
ssoEnabled = PropertiesUtil.getBooleanProperty("ranger.sso.enabled", false);
headerAuthEnabled = PropertiesUtil.getBooleanProperty(PROP_HEADER_AUTH_ENABLED, false);
}

public UserSessionBase processSuccessLogin(int authType, String userAgent, HttpServletRequest httpRequest) {
boolean newSessionCreation = true;
UserSessionBase userSession = null;
Expand Down Expand Up @@ -523,18 +535,23 @@ protected XXAuthSession storeAuthSession(XXAuthSession gjAuthSession) {
}

private void getSSOSpnegoAuthCheckForAPI(String currentLoginId, HttpServletRequest request) {
RangerSecurityContext context = RangerContextHolder.getSecurityContext();
UserSessionBase session = context != null ? context.getUserSession() : null;
boolean ssoEnabled = session != null ? session.isSSOEnabled() : PropertiesUtil.getBooleanProperty("ranger.sso.enabled", false);
XXPortalUser gjUser = daoManager.getXXPortalUser().findByLoginId(currentLoginId);
XXPortalUser gjUser = daoManager.getXXPortalUser().findByLoginId(currentLoginId);

if (gjUser == null && ((request.getAttribute("spnegoEnabled") != null && (boolean) request.getAttribute("spnegoEnabled")) || (ssoEnabled) || bizUtil.isHealthCheckUser(currentLoginId))) {
logger.debug("User : {} doesn't exist in Ranger DB So creating user as it's SSO or Spnego authenticated", currentLoginId);
if (gjUser == null) {
if (headerAuthEnabled || bizUtil.isHealthCheckUser(currentLoginId)) {
logger.debug("User : {} doesn't exist in Ranger DB. Creating user as it's header-auth authenticated", currentLoginId);

if (bizUtil.isHealthCheckUser(currentLoginId)) {
xUserMgr.createServiceConfigUserSynchronously(currentLoginId);
} else {
xUserMgr.createServiceConfigUser(currentLoginId);
RangerSecurityContext context = RangerContextHolder.getSecurityContext();
UserSessionBase session = context != null ? context.getUserSession() : null;
boolean ssoEnabled = session != null ? session.isSSOEnabled() : this.ssoEnabled;

if (ssoEnabled || (request.getAttribute("spnegoEnabled") != null && (boolean) request.getAttribute("spnegoEnabled"))) {
logger.debug("User : {} doesn't exist in Ranger DB. Creating user as it's SSO or Spnego", currentLoginId);

xUserMgr.createServiceConfigUser(currentLoginId);
}
}
}
}
Expand Down
88 changes: 84 additions & 4 deletions security-admin/src/main/java/org/apache/ranger/biz/XUserMgr.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import org.apache.ranger.db.XXUserDao;
import org.apache.ranger.db.XXUserPermissionDao;
import org.apache.ranger.entity.XXAuditMap;
import org.apache.ranger.entity.XXAuthSession;
import org.apache.ranger.entity.XXGroup;
import org.apache.ranger.entity.XXGroupPermission;
import org.apache.ranger.entity.XXGroupUser;
Expand Down Expand Up @@ -79,6 +80,10 @@
import org.apache.ranger.plugin.store.EmbeddedServiceDefsUtil;
import org.apache.ranger.plugin.util.PasswordUtils.PasswordGenerator;
import org.apache.ranger.plugin.util.RangerUserStore;
import org.apache.ranger.security.context.RangerContextHolder;
import org.apache.ranger.security.context.RangerSecurityContext;
import org.apache.ranger.security.web.filter.RangerAuthenticationToken;
import org.apache.ranger.security.web.filter.RangerHeaderPreAuthFilter;
import org.apache.ranger.service.RangerPolicyService;
import org.apache.ranger.service.XPortalUserService;
import org.apache.ranger.service.XResourceService;
Expand Down Expand Up @@ -110,6 +115,9 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
Expand Down Expand Up @@ -3782,6 +3790,72 @@ private void updateUserStoreVersion(String label) {
}
}

/**
* Resolves the roles to assign to a freshly auto-provisioned external user. When header-based
* authentication is enabled and the current request was authenticated through the trusted proxy,
* the (validated) roles carried in the {@link RangerAuthenticationToken} authorities are used.
* Returns {@code null} otherwise, in which case the caller falls back to the default ROLE_USER.
*/
private List<String> getHeaderAuthRoles() {
List<String> ret = null;

if (PropertiesUtil.getBooleanProperty(RangerHeaderPreAuthFilter.PROP_HEADER_AUTH_ENABLED, false)) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();

if (auth instanceof RangerAuthenticationToken && ((RangerAuthenticationToken) auth).getAuthType() == XXAuthSession.AUTH_TYPE_TRUSTED_PROXY) {
List<String> roles = new ArrayList<>();

for (GrantedAuthority authority : auth.getAuthorities()) {
String role = (authority != null) ? StringUtils.trimToNull(authority.getAuthority()) : null;

if (role != null && RangerConstants.VALID_USER_ROLE_LIST.contains(role)) {
roles.add(role);
}
}

if (!roles.isEmpty()) {
ret = roles;
}
}
}

return ret;
}

/**
* Creates the portal user with the supplied roles. When the roles originate from the trusted
* roles header ({@code trustedHeaderRoles}), the creation runs under a trusted system session
* (an admin session with no interactive user attached) so that non-public roles such as
* ROLE_SYS_ADMIN carried in the header are permitted during provisioning. No real user account
* is impersonated and the previous security context is always restored.
*/
private XXPortalUser createExternalPortalUser(XXPortalUser xXPortalUser, List<String> roleList, boolean trustedHeaderRoles) {
if (!trustedHeaderRoles) {
return userMgr.createUser(xXPortalUser, RangerCommonEnums.STATUS_ENABLED, roleList);
}

RangerSecurityContext context = RangerContextHolder.getSecurityContext();
UserSessionBase originalSession = context != null ? context.getUserSession() : null;

if (context == null) {
context = new RangerSecurityContext();

RangerContextHolder.setSecurityContext(context);
}

try {
UserSessionBase systemSession = new UserSessionBase();

systemSession.setUserAdmin(true);

context.setUserSession(systemSession);

return userMgr.createUser(xXPortalUser, RangerCommonEnums.STATUS_ENABLED, roleList);
} finally {
context.setUserSession(originalSession);
}
}

private class ExternalUserCreator implements Runnable {
private final String userName;

Expand All @@ -3807,18 +3881,24 @@ private void createExternalUser() {
vXPortalUser.setLoginId(userName);
vXPortalUser.setUserSource(RangerCommonEnums.USER_EXTERNAL);

ArrayList<String> roleList = new ArrayList<>();
List<String> headerRoles = getHeaderAuthRoles();
boolean trustedHeaderRoles = CollectionUtils.isNotEmpty(headerRoles);
ArrayList<String> roleList = new ArrayList<>();

roleList.add(RangerConstants.ROLE_USER);
if (trustedHeaderRoles) {
roleList.addAll(headerRoles);
} else {
roleList.add(RangerConstants.ROLE_USER);
}

vXPortalUser.setUserRoleList(roleList);

xXPortalUser = userMgr.mapVXPortalUserToXXPortalUser(vXPortalUser);

try {
xXPortalUser = userMgr.createUser(xXPortalUser, RangerCommonEnums.STATUS_ENABLED, roleList);
xXPortalUser = createExternalPortalUser(xXPortalUser, roleList, trustedHeaderRoles);

logger.debug("createExternalUser(): Successfully created user in x_portal_user table {}", xXPortalUser.getLoginId());
logger.debug("createExternalUser(): Successfully created user {} in x_portal_user table with roles {}", xXPortalUser.getLoginId(), roleList);
} catch (Exception ex) {
throw new RuntimeException("Failed to create user " + userName + " in x_portal_user table. retrying", ex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,8 @@ public class RangerConstants extends RangerCommonEnums {
// Default Roles
public static final String ROLE_SYS_ADMIN = "ROLE_SYS_ADMIN";
public static final String ROLE_ADMIN = "ROLE_ADMIN";
public static final String ROLE_INTEGRATOR = "ROLE_INTEGRATOR";
public static final String ROLE_DATA_ANALYST = "ROLE_DATA_ANALYST";
public static final String ROLE_BIZ_MGR = "ROLE_BIZ_MGR";
public static final String ROLE_KEY_ADMIN = "ROLE_KEY_ADMIN";
public static final String ROLE_USER = "ROLE_USER";
public static final String ROLE_ANON = "ROLE_ANON";
public static final String ROLE_OTHER = "ROLE_OTHER";
public static final String GROUP_PUBLIC = "public";
public static final String ROLE_ADMIN_AUDITOR = "ROLE_ADMIN_AUDITOR";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.ranger.biz.UserMgr;
import org.apache.ranger.common.PropertiesUtil;
import org.apache.ranger.common.RangerConstants;
import org.apache.ranger.entity.XXAuthSession;
import org.apache.ranger.plugin.util.SpiffeIdUtil;
import org.slf4j.Logger;
Expand All @@ -45,7 +46,10 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class RangerHeaderPreAuthFilter extends GenericFilterBean {
private static final Logger LOG = LoggerFactory.getLogger(RangerHeaderPreAuthFilter.class);
Expand All @@ -54,10 +58,30 @@ public class RangerHeaderPreAuthFilter extends GenericFilterBean {
public static final String PROP_USERNAME_HEADER_NAME = "ranger.admin.authn.header.username";
public static final String PROP_SPIFFE_HEADER_NAME = "ranger.admin.authn.header.spiffe";
public static final String PROP_REQUEST_ID_HEADER_NAME = "ranger.admin.authn.header.requestid";
public static final String PROP_ROLES_HEADER_NAME = "ranger.admin.authn.header.roles";

/**
* External-facing role names accepted in the configured roles header, mapped to Ranger's
* internal role constants (see {@link RangerConstants#VALID_USER_ROLE_LIST}).
*/
private static final Map<String, String> EXTERNAL_ROLE_TO_RANGER_ROLE;

static {
Map<String, String> roleMap = new HashMap<>();

roleMap.put("RANGER_ROLE_ADMIN", RangerConstants.ROLE_SYS_ADMIN);
roleMap.put("RANGER_ROLE_AUDITOR", RangerConstants.ROLE_ADMIN_AUDITOR);
roleMap.put("RANGER_ROLE_USER", RangerConstants.ROLE_USER);
roleMap.put("RANGER_ROLE_KEY_ADMIN", RangerConstants.ROLE_KEY_ADMIN);
roleMap.put("RANGER_ROLE_KEY_ADMIN_AUDITOR", RangerConstants.ROLE_KEY_ADMIN_AUDITOR);

EXTERNAL_ROLE_TO_RANGER_ROLE = Collections.unmodifiableMap(roleMap);
}

private boolean headerAuthEnabled;
private String userNameHeaderName;
private List<String> spiffeHeaderNames;
private String rolesHeaderName;

@Autowired
UserMgr userMgr;
Expand All @@ -69,6 +93,7 @@ protected void initialize() {
if (headerAuthEnabled) {
userNameHeaderName = PropertiesUtil.getProperty(PROP_USERNAME_HEADER_NAME);
spiffeHeaderNames = SpiffeIdUtil.parseHeaderNames(PropertiesUtil.getProperty(PROP_SPIFFE_HEADER_NAME));
rolesHeaderName = PropertiesUtil.getProperty(PROP_ROLES_HEADER_NAME);

if (StringUtils.isBlank(userNameHeaderName) && spiffeHeaderNames.isEmpty()) {
LOG.warn("Disabling header-based authentication, as neither {} nor {} is set", PROP_USERNAME_HEADER_NAME, PROP_SPIFFE_HEADER_NAME);
Expand All @@ -88,7 +113,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
String username = resolvePrincipal(httpRequest);

if (StringUtils.isNotBlank(username)) {
List<GrantedAuthority> grantedAuthorities = getAuthoritiesFromRanger(username);
List<GrantedAuthority> grantedAuthorities = getAuthorities(httpRequest, username);
final UserDetails principal = new User(username, "", grantedAuthorities);
RangerAuthenticationToken authToken = new RangerAuthenticationToken(principal, grantedAuthorities, XXAuthSession.AUTH_TYPE_TRUSTED_PROXY);

Expand Down Expand Up @@ -135,14 +160,82 @@ private String resolvePrincipal(HttpServletRequest httpRequest) {
return null;
}

/**
* Resolves the authorities to assign to the authenticated user. When the trusted proxy
* supplies roles via the configured roles header, those roles are honored; otherwise the
* roles persisted for the user in the Ranger DB are used.
*/
private List<GrantedAuthority> getAuthorities(HttpServletRequest httpRequest, String username) {
List<GrantedAuthority> ret = getAuthoritiesFromHeader(httpRequest);

if (ret == null || ret.isEmpty()) {
ret = getAuthoritiesFromRanger(username);
}

return ret == null ? Collections.emptyList() : ret;
}

/**
* Loads authorities from the configured roles header. External-facing role names
* ({@code RANGER_ROLE_ADMIN}, {@code RANGER_ROLE_AUDITOR}, etc.) are mapped to Ranger's
* internal role constants before being added to the authentication token. Internal role
* names from {@link RangerConstants#VALID_USER_ROLE_LIST} are also accepted; any other
* value is ignored.
*/
private List<GrantedAuthority> getAuthoritiesFromHeader(HttpServletRequest httpRequest) {
List<GrantedAuthority> ret = null;

if (StringUtils.isNotBlank(rolesHeaderName)) {
String rolesHeaderValue = httpRequest.getHeader(rolesHeaderName);

if (StringUtils.isNotBlank(rolesHeaderValue)) {
ret = new ArrayList<>();

for (String role : rolesHeaderValue.split(",")) {
String trimmedRole = StringUtils.trimToNull(role);

if (trimmedRole != null) {
String rangerRole = resolveRoleFromHeader(trimmedRole);

if (rangerRole != null) {
ret.add(new SimpleGrantedAuthority(rangerRole));
} else {
LOG.warn("Ignoring unrecognized role '{}' received in header '{}'", trimmedRole, rolesHeaderName);
}
}
}
}
}

return ret;
}

/**
* Maps an external-facing role name from the roles header to a Ranger internal role constant,
* or returns the value unchanged when it is already a recognized internal role name.
*/
private String resolveRoleFromHeader(String headerRole) {
String ret = EXTERNAL_ROLE_TO_RANGER_ROLE.get(headerRole);

if (ret == null) {
if (RangerConstants.VALID_USER_ROLE_LIST.contains(headerRole)) {
ret = headerRole;
}
}

return ret;
}

/**
* Loads authorities from Ranger DB
*/
private List<GrantedAuthority> getAuthoritiesFromRanger(String username) {
List<GrantedAuthority> ret = new ArrayList<>();
List<GrantedAuthority> ret = null;
Collection<String> roleList = userMgr.getRolesByLoginId(username);

if (roleList != null) {
if (roleList != null && !roleList.isEmpty()) {
ret = new ArrayList<>();

for (String role : roleList) {
if (StringUtils.isNotBlank(role)) {
ret.add(new SimpleGrantedAuthority(role));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,11 @@
<name>ranger.admin.authn.header.requestid</name>
<value></value>
</property>
<property>
<name>ranger.admin.authn.header.roles</name>
<value></value>
<description>Name of the HTTP header (e.g. X-Forwarded-Roles) carrying a comma-separated list of roles to assign to the authenticated user. Accepted values are role names RANGER_ROLE_ADMIN, RANGER_ROLE_AUDITOR, RANGER_ROLE_USER, RANGER_ROLE_KEY_ADMIN, and RANGER_ROLE_KEY_ADMIN_AUDITOR. Values that are not recognized are ignored.</description>
</property>
<property>
<name>ranger.admin.spiffe.as.username.enabled</name>
<value>false</value>
Expand Down
Loading