This is an automated email from the ASF dual-hosted git repository.
kumaab pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ranger.git
The following commit(s) were added to refs/heads/master by this push:
new e3adde517 RANGER-5717: Introduce health check REST APIs in Ranger
Admin (#1120)
e3adde517 is described below
commit e3adde517fc566a11cb29631849e048f6503e459
Author: Abhishek Kumar <[email protected]>
AuthorDate: Mon Aug 3 19:44:06 2026 -0700
RANGER-5717: Introduce health check REST APIs in Ranger Admin (#1120)
---
.cursor/rules/ranger-checkstyle.mdc | 78 ++++++++++++++++++++
.../ranger/plugin/model/RangerServerHealth.java | 4 +
dev-support/ranger-docker/Dockerfile.ranger | 1 +
.../admin/ranger-admin-install-mysql.properties | 3 +
.../admin/ranger-admin-install-oracle.properties | 3 +
.../admin/ranger-admin-install-postgres.properties | 3 +
.../ranger-admin-install-sqlserver.properties | 3 +
.../java/org/apache/ranger/biz/RangerBizUtil.java | 6 ++
.../java/org/apache/ranger/biz/SessionMgr.java | 18 ++++-
.../java/org/apache/ranger/db/XXServiceDefDao.java | 4 +
.../org/apache/ranger/rest/RangerHealthREST.java | 41 ++++++++++-
.../ranger/service/RangerServiceDefService.java | 5 ++
.../apache/ranger/util/RangerServerHealthUtil.java | 65 +++++++++++++++++
.../main/resources/META-INF/jpa_named_queries.xml | 4 +
.../main/resources/conf.dist/ranger-admin-site.xml | 8 +-
.../conf.dist/security-applicationContext.xml | 1 +
.../java/org/apache/ranger/biz/TestSessionMgr.java | 37 ++++++++++
.../apache/ranger/rest/TestRangerHealthREST.java | 73 ++++++++++++++++++-
.../ranger/util/TestRangerServerHealthUtil.java | 85 +++++++++++++++++++++-
19 files changed, 430 insertions(+), 12 deletions(-)
diff --git a/.cursor/rules/ranger-checkstyle.mdc
b/.cursor/rules/ranger-checkstyle.mdc
new file mode 100644
index 000000000..7788bcf37
--- /dev/null
+++ b/.cursor/rules/ranger-checkstyle.mdc
@@ -0,0 +1,78 @@
+---
+description: Apache Ranger code must pass Checkstyle on Maven verify
+globs: **/*.java
+alwaysApply: false
+---
+
+<!--
+ 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.
+-->
+
+# Ranger Checkstyle (mandatory on build)
+
+Ranger runs **maven-checkstyle-plugin** in the **`verify`** phase (`check`
goal). Violations **fail the build** when `checkstyle.failOnViolation` is true.
Main and **test** sources are checked (`includeTestSourceDirectory` is enabled).
+
+**Authoritative config** (read before large Java edits or refactors):
+
+- `dev-support/checkstyle.xml` — active rules
+- `dev-support/checkstyle-suppressions.xml` — per-file/line suppressions
+
+After substantive Java changes, run Checkstyle for the module, for example:
+
+`mvn -pl <module> -DskipTests verify`
+
+(Or a narrower goal if the project exposes one; `verify` is what CI uses.)
+
+## Practical reminders (config has many more rules)
+
+- Use **LF** line endings, **no tabs**; one **newline at end of file**; **no
trailing whitespace**.
+- **No** multiple consecutive **blank** lines; **no** blank line immediately
after `{` or before `}` in the patterns enforced by the config.
+- **Limit static imports**: e.g. avoid static imports of `Optional.*`, and
avoid certain static `of` / `copyOf` / `valueOf` / `builder` patterns (see
`RegexpSingleline` rules in `checkstyle.xml`).
+
+When unsure, match the style of neighboring classes in the same module and
re-run `verify` on the touched module.
+
+## Single return (Ranger convention)
+
+Reviewers expect **one `return` per method** in production code (guard clauses
with early return are avoided). Assign into locals and return once at the end.
+
+```java
+// Prefer
+private String resolveAuthenticatedLoginId() {
+ String loginId = bizUtil.getCurrentUserLoginId();
+
+ if (loginId == null) {
+ Object authentication =
SecurityContextHolder.getContext().getAuthentication();
+
+ if (authentication instanceof RangerAuthenticationToken) {
+ RangerAuthenticationToken token = (RangerAuthenticationToken)
authentication;
+
+ if (token.getAuthType() == XXAuthSession.AUTH_TYPE_TRUSTED_PROXY) {
+ loginId = token.getName();
+ }
+ }
+ }
+
+ return loginId;
+}
+
+// Avoid multiple returns
+// if (loginId != null) { return loginId; }
+// ...
+// return null;
+```
+
+Same idea applies to other return types: use a `result` variable (or reuse an
existing one), set it in branches, then `return result` once.
diff --git
a/agents-common/src/main/java/org/apache/ranger/plugin/model/RangerServerHealth.java
b/agents-common/src/main/java/org/apache/ranger/plugin/model/RangerServerHealth.java
index 781238232..ebecd9c91 100644
---
a/agents-common/src/main/java/org/apache/ranger/plugin/model/RangerServerHealth.java
+++
b/agents-common/src/main/java/org/apache/ranger/plugin/model/RangerServerHealth.java
@@ -59,6 +59,10 @@ public static Builder down() {
return status(RangerServerStatus.DOWN);
}
+ public static Builder initFailure() {
+ return status(RangerServerStatus.INITIALIZATION_FAILURE);
+ }
+
public static Builder status(RangerServerStatus status) {
return new Builder(status);
}
diff --git a/dev-support/ranger-docker/Dockerfile.ranger
b/dev-support/ranger-docker/Dockerfile.ranger
index 4f00522a7..b8e193df9 100644
--- a/dev-support/ranger-docker/Dockerfile.ranger
+++ b/dev-support/ranger-docker/Dockerfile.ranger
@@ -54,6 +54,7 @@ COPY ./downloads/mssql-jdbc-12.8.1.jre8.jar
/home/ranger/dist/
RUN mv /home/ranger/dist/mssql-jdbc-12.8.1.jre8.jar
/usr/share/java/mssql.jar
FROM ranger_${RANGER_DB_TYPE}
+ENV RANGER_CONF_DIR=${RANGER_HOME}/admin/ews/webapp/WEB-INF/classes/conf
USER ranger
diff --git
a/dev-support/ranger-docker/scripts/admin/ranger-admin-install-mysql.properties
b/dev-support/ranger-docker/scripts/admin/ranger-admin-install-mysql.properties
index 14b4330ad..ec1b05110 100644
---
a/dev-support/ranger-docker/scripts/admin/ranger-admin-install-mysql.properties
+++
b/dev-support/ranger-docker/scripts/admin/ranger-admin-install-mysql.properties
@@ -110,3 +110,6 @@ audit_jaas_client_option_useTicketCache=true
audit_jaas_client_option_serviceName=ranger
audit_jaas_client_option_keyTab=/etc/keytabs/rangeradmin.keytab
audit_jaas_client_option_principal=rangeradmin/[email protected]
+
+#-- SSO Configs --#
+sso_enabled=true
diff --git
a/dev-support/ranger-docker/scripts/admin/ranger-admin-install-oracle.properties
b/dev-support/ranger-docker/scripts/admin/ranger-admin-install-oracle.properties
index 4748dc0dc..e5d81452e 100644
---
a/dev-support/ranger-docker/scripts/admin/ranger-admin-install-oracle.properties
+++
b/dev-support/ranger-docker/scripts/admin/ranger-admin-install-oracle.properties
@@ -111,3 +111,6 @@ audit_jaas_client_option_useTicketCache=true
audit_jaas_client_option_serviceName=ranger
audit_jaas_client_option_keyTab=/etc/keytabs/rangeradmin.keytab
audit_jaas_client_option_principal=rangeradmin/[email protected]
+
+#-- SSO Configs --#
+sso_enabled=true
diff --git
a/dev-support/ranger-docker/scripts/admin/ranger-admin-install-postgres.properties
b/dev-support/ranger-docker/scripts/admin/ranger-admin-install-postgres.properties
index 0b1d3f560..a66fb58b5 100644
---
a/dev-support/ranger-docker/scripts/admin/ranger-admin-install-postgres.properties
+++
b/dev-support/ranger-docker/scripts/admin/ranger-admin-install-postgres.properties
@@ -126,3 +126,6 @@ audit_jaas_client_option_serviceName=
audit_jaas_client_option_useTicketCache=false
audit_jaas_client_option_keyTab=/etc/keytabs/rangeradmin.keytab
audit_jaas_client_option_principal=rangeradmin/[email protected]
+
+#-- SSO Configs --#
+sso_enabled=true
diff --git
a/dev-support/ranger-docker/scripts/admin/ranger-admin-install-sqlserver.properties
b/dev-support/ranger-docker/scripts/admin/ranger-admin-install-sqlserver.properties
index 40f77c3fd..87c36a035 100644
---
a/dev-support/ranger-docker/scripts/admin/ranger-admin-install-sqlserver.properties
+++
b/dev-support/ranger-docker/scripts/admin/ranger-admin-install-sqlserver.properties
@@ -117,3 +117,6 @@ audit_jaas_client_1_option_useTicketCache=true
audit_jaas_client_1_option_serviceName=ranger
audit_jaas_client_1_option_keyTab=/etc/keytabs/rangeradmin.keytab
audit_jaas_client_1_option_principal=rangeradmin/[email protected]
+
+#-- SSO Configs --#
+sso_enabled=true
diff --git
a/security-admin/src/main/java/org/apache/ranger/biz/RangerBizUtil.java
b/security-admin/src/main/java/org/apache/ranger/biz/RangerBizUtil.java
index 2c2b665ca..9219a86a1 100644
--- a/security-admin/src/main/java/org/apache/ranger/biz/RangerBizUtil.java
+++ b/security-admin/src/main/java/org/apache/ranger/biz/RangerBizUtil.java
@@ -88,6 +88,8 @@ public class RangerBizUtil {
public static final String AUDIT_STORE_ELASTIC_SEARCH = "elasticSearch";
public static final String AUDIT_STORE_OPENSEARCH = "opensearch";
public static final String AUDIT_STORE_CLOUD_WATCH = "cloudwatch";
+ // login id used by the Ranger Admin health readiness endpoint
+ public static final String HEALTHCHECK_USERNAME =
RangerAdminConfig.getInstance().get("ranger.admin.healthcheck.username",
"healthcheck");
public static final boolean BATCH_CLEAR_ENABLED =
PropertiesUtil.getBooleanProperty("ranger.jpa.jdbc.batch-clear.enable", true);
public static final int POLICY_BATCH_SIZE =
PropertiesUtil.getIntProperty("ranger.jpa.jdbc.batch-clear.size", 10);
public static final int BATCH_PERSIST_SIZE =
PropertiesUtil.getIntProperty("ranger.jpa.jdbc.batch-persist.size", 500);
@@ -1170,6 +1172,10 @@ public boolean isUserRangerAdmin(String username) {
return isAdmin;
}
+ public boolean isHealthCheckUser(String user) {
+ return HEALTHCHECK_USERNAME.equalsIgnoreCase(user);
+ }
+
/**
* True when the current session is a config super-user per
* {@code ranger.admin.super.users} / {@code ranger.admin.super.groups}.
diff --git a/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java
b/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java
index 9b092b2f6..519a89155 100644
--- a/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java
+++ b/security-admin/src/main/java/org/apache/ranger/biz/SessionMgr.java
@@ -82,6 +82,9 @@ public class SessionMgr {
@Autowired
RangerDaoManager daoManager;
+ @Autowired
+ RangerBizUtil bizUtil;
+
@Autowired
XUserMgr xUserMgr;
@@ -507,10 +510,15 @@ protected boolean validateUserSession(UserSessionBase
userSession, String curren
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
protected XXAuthSession storeAuthSession(XXAuthSession gjAuthSession) {
- // daoManager.getEntityManager().getTransaction().begin();
+ /*
+ Recording an x_auth_sess row for every liveness/readiness probe is not
required
+ */
+ if (gjAuthSession != null &&
bizUtil.isHealthCheckUser(gjAuthSession.getLoginId())) {
+ return gjAuthSession;
+ }
+
XXAuthSession dbMAuthSession =
daoManager.getXXAuthSession().create(gjAuthSession);
- // daoManager.getEntityManager().getTransaction().commit();
return dbMAuthSession;
}
@@ -523,7 +531,11 @@ private void getSSOSpnegoAuthCheckForAPI(String
currentLoginId, HttpServletReque
if (gjUser == null && ((request.getAttribute("spnegoEnabled") != null
&& (boolean) request.getAttribute("spnegoEnabled")) || (ssoEnabled))) {
logger.debug("User : {} doesn't exist in Ranger DB So creating
user as it's SSO or Spnego authenticated", currentLoginId);
- xUserMgr.createServiceConfigUser(currentLoginId);
+ if (bizUtil.isHealthCheckUser(currentLoginId)) {
+ xUserMgr.createServiceConfigUserSynchronously(currentLoginId);
+ } else {
+ xUserMgr.createServiceConfigUser(currentLoginId);
+ }
}
}
diff --git
a/security-admin/src/main/java/org/apache/ranger/db/XXServiceDefDao.java
b/security-admin/src/main/java/org/apache/ranger/db/XXServiceDefDao.java
index 3811be5d4..af2fafbd0 100644
--- a/security-admin/src/main/java/org/apache/ranger/db/XXServiceDefDao.java
+++ b/security-admin/src/main/java/org/apache/ranger/db/XXServiceDefDao.java
@@ -126,6 +126,10 @@ public List<String> findAllHavingEnrichers() {
return
getEntityManager().createNamedQuery("XXServiceDef.getNameByHasEnricher",
String.class).getResultList();
}
+ public List<String> findAllNames() {
+ return getEntityManager().createNamedQuery("XXServiceDef.getAllNames",
String.class).getResultList();
+ }
+
/**
* Fetch and return count of services for each service type/ def
*
diff --git
a/security-admin/src/main/java/org/apache/ranger/rest/RangerHealthREST.java
b/security-admin/src/main/java/org/apache/ranger/rest/RangerHealthREST.java
index 2798866e9..439e5187f 100644
--- a/security-admin/src/main/java/org/apache/ranger/rest/RangerHealthREST.java
+++ b/security-admin/src/main/java/org/apache/ranger/rest/RangerHealthREST.java
@@ -20,6 +20,7 @@
package org.apache.ranger.rest;
import org.apache.ranger.biz.RangerBizUtil;
+import org.apache.ranger.common.RESTErrorUtil;
import org.apache.ranger.plugin.model.RangerServerHealth;
import org.apache.ranger.util.RangerServerHealthUtil;
import org.springframework.beans.factory.annotation.Autowired;
@@ -28,20 +29,26 @@
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
+import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
+import java.util.List;
+
@Path("actuator")
@Component
@Scope("request")
public class RangerHealthREST {
@Autowired
- RangerBizUtil xaBizUtil;
+ RangerBizUtil bizUtil;
@Autowired
RangerServerHealthUtil rangerServerHealthUtil;
+ @Autowired
+ RESTErrorUtil restErrorUtil;
+
/*
This API is used to get the Health check of the Ranger Admin
*/
@@ -50,8 +57,38 @@ public class RangerHealthREST {
@Produces("application/json")
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public RangerServerHealth getRangerServerHealth() {
- String dbVersion = xaBizUtil.getDBVersion();
+ String dbVersion = bizUtil.getDBVersion();
return rangerServerHealthUtil.getRangerServerHealth(dbVersion);
}
+
+ @GET
+ @Path("/health/readiness")
+ @Produces("application/json")
+ @Transactional(propagation = Propagation.NOT_SUPPORTED)
+ public RangerServerHealth getRangerServerReadiness() {
+ if
(!bizUtil.isHealthCheckUser(rangerServerHealthUtil.resolveAuthenticatedLoginId()))
{
+ throw
restErrorUtil.createRESTException(HttpServletResponse.SC_FORBIDDEN,
+ "Only the healthcheck user may query service-def names via
this path.", true);
+ }
+
+ List<String> serviceDefNames =
rangerServerHealthUtil.getServiceDefNames();
+ RangerServerHealth serverHealth;
+
+ if (serviceDefNames != null && !serviceDefNames.isEmpty()) {
+ serverHealth =
rangerServerHealthUtil.serviceUpWithAvailableServiceDefs(serviceDefNames);
+ } else {
+ serverHealth = rangerServerHealthUtil.serviceInitFailure();
+ }
+
+ return serverHealth;
+ }
+
+ @GET
+ @Path("/health/liveness")
+ @Produces("application/json")
+ @Transactional(propagation = Propagation.NOT_SUPPORTED)
+ public RangerServerHealth getRangerServerLiveness() {
+ return rangerServerHealthUtil.serviceUp();
+ }
}
diff --git
a/security-admin/src/main/java/org/apache/ranger/service/RangerServiceDefService.java
b/security-admin/src/main/java/org/apache/ranger/service/RangerServiceDefService.java
index 78e269aa6..c77a8a22d 100644
---
a/security-admin/src/main/java/org/apache/ranger/service/RangerServiceDefService.java
+++
b/security-admin/src/main/java/org/apache/ranger/service/RangerServiceDefService.java
@@ -19,6 +19,7 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.ranger.authorization.hadoop.config.RangerAdminConfig;
+import org.apache.ranger.db.XXServiceDefDao;
import org.apache.ranger.entity.XXServiceDef;
import org.apache.ranger.plugin.model.RangerServiceDef;
import
org.apache.ranger.plugin.model.RangerServiceDef.RangerPolicyConditionDef;
@@ -60,6 +61,10 @@ public List<RangerServiceDef> getAllServiceDefs() {
return serviceDefList;
}
+ public List<String> getAllServiceDefNames() {
+ return ((XXServiceDefDao) getDao()).findAllNames();
+ }
+
public RangerServiceDef getPopulatedViewObject(XXServiceDef xServiceDef) {
return this.populateViewBean(xServiceDef);
}
diff --git
a/security-admin/src/main/java/org/apache/ranger/util/RangerServerHealthUtil.java
b/security-admin/src/main/java/org/apache/ranger/util/RangerServerHealthUtil.java
index 8eb3c1029..5ff3fc85b 100644
---
a/security-admin/src/main/java/org/apache/ranger/util/RangerServerHealthUtil.java
+++
b/security-admin/src/main/java/org/apache/ranger/util/RangerServerHealthUtil.java
@@ -22,11 +22,21 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.ranger.biz.RangerBizUtil;
import org.apache.ranger.common.AppConstants;
+import org.apache.ranger.common.UserSessionBase;
+import org.apache.ranger.entity.XXAuthSession;
import org.apache.ranger.plugin.model.RangerServerHealth;
+import org.apache.ranger.security.web.filter.RangerAuthenticationToken;
+import org.apache.ranger.service.RangerServiceDefService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -35,15 +45,24 @@
@Component
public class RangerServerHealthUtil {
+ private static final Logger LOG =
LoggerFactory.getLogger(RangerServerHealthUtil.class);
+
private static final String COMPONENTS = "components";
private static final String STATUS = "status";
private static final String DETAILS = "details";
+ private static final String SERVICE_DEFS = "service-defs";
private static final String DB = "db";
private static final String DB_FLAVOR = "flavor";
private static final String DB_VERSION = "version";
private static final String DB_VALIDATION_QUERY = "validationQuery";
private static final String NOT_AVAILABLE = "Not Available";
+ @Autowired
+ RangerBizUtil bizUtil;
+
+ @Autowired
+ RangerServiceDefService serviceDefService;
+
/* RangerAdmin Health Check JSON Response look like
{
"status": "UP",
@@ -62,6 +81,19 @@ In the future this health check API can be extended for
other components like au
}
*/
+ public RangerServerHealth serviceUp() {
+ return RangerServerHealth.up().build();
+ }
+
+ public RangerServerHealth serviceInitFailure() {
+ return RangerServerHealth.initFailure().build();
+ }
+
+ public RangerServerHealth serviceUpWithAvailableServiceDefs(List<String>
serviceDefNames) {
+ Map<String, Object> components =
Collections.singletonMap(SERVICE_DEFS, serviceDefNames);
+ return RangerServerHealth.up().withDetail(COMPONENTS,
components).build();
+ }
+
public RangerServerHealth getRangerServerHealth(String dbVersion) {
Map<String, Object> components = new HashMap<>();
Map<String, Object> dbStatus = getDbStatus(dbVersion);
@@ -98,4 +130,37 @@ private Map<String, Object> getDbStatus(String dbVersion) {
return ret;
}
+
+ public List<String> getServiceDefNames() {
+ LOG.debug("==> RangerServerHealthUtil.getServiceDefNames()");
+
+ List<String> ret = serviceDefService.getAllServiceDefNames();
+
+ LOG.debug("<== RangerServerHealthUtil.getServiceDefNames(): count={}",
(ret == null ? 0 : ret.size()));
+
+ return ret;
+ }
+
+ /**
+ * Header-based auth sets {@link RangerAuthenticationToken} before Ranger
+ * {@link UserSessionBase} is available. Fall back to that token when the
Ranger session
+ * has not been materialized yet (e.g. healthcheck user not in DB).
+ */
+ public String resolveAuthenticatedLoginId() {
+ String loginId = bizUtil.getCurrentUserLoginId();
+
+ if (loginId == null) {
+ Object authentication =
SecurityContextHolder.getContext().getAuthentication();
+
+ if (authentication instanceof RangerAuthenticationToken) {
+ RangerAuthenticationToken token = (RangerAuthenticationToken)
authentication;
+
+ if (token.getAuthType() ==
XXAuthSession.AUTH_TYPE_TRUSTED_PROXY) {
+ loginId = token.getName();
+ }
+ }
+ }
+
+ return loginId;
+ }
}
diff --git a/security-admin/src/main/resources/META-INF/jpa_named_queries.xml
b/security-admin/src/main/resources/META-INF/jpa_named_queries.xml
index b5e058863..65607931e 100755
--- a/security-admin/src/main/resources/META-INF/jpa_named_queries.xml
+++ b/security-admin/src/main/resources/META-INF/jpa_named_queries.xml
@@ -558,6 +558,10 @@
<query>SELECT DISTINCT(xsd.name) FROM XXServiceDef xsd,
XXContextEnricherDef xced WHERE xsd.id = xced.defId</query>
</named-query>
+ <named-query name="XXServiceDef.getAllNames">
+ <query>SELECT obj.name FROM XXServiceDef obj ORDER BY
obj.name</query>
+ </named-query>
+
<named-query name="XXServiceDef.getServiceCount">
<query>SELECT xsd.name, COUNT(xs.id) FROM XXService xs,
XXServiceDef xsd WHERE xs.type = xsd.id GROUP BY xsd.name</query>
</named-query>
diff --git a/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml
b/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml
index 5493b69ba..d327f3167 100644
--- a/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml
+++ b/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml
@@ -301,10 +301,6 @@
<name>ranger.sso.providerurl</name>
<value>https://127.0.0.1:8443/gateway/knoxsso/api/v1/websso</value>
</property>
- <property>
- <name>ranger.sso.publicKey</name>
- <value></value>
- </property>
<property>
<name>ranger.sso.enabled</name>
<value>false</value>
@@ -314,6 +310,10 @@
<value>Mozilla,chrome</value>
</property>
<!-- SSO Properties Ends-->
+ <property>
+ <name>ranger.admin.healthcheck.username</name>
+ <value>healthcheck</value>
+ </property>
<!-- Trusted header auth properties start -->
<property>
<name>ranger.admin.authn.header.enabled</name>
diff --git
a/security-admin/src/main/resources/conf.dist/security-applicationContext.xml
b/security-admin/src/main/resources/conf.dist/security-applicationContext.xml
index a0c8ac0fc..b99a5537c 100644
---
a/security-admin/src/main/resources/conf.dist/security-applicationContext.xml
+++
b/security-admin/src/main/resources/conf.dist/security-applicationContext.xml
@@ -51,6 +51,7 @@
http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd">
<security:http pattern="/service/roles/download/**" security="none"/>
<security:http pattern="/service/xusers/download/**" security="none"/>
<security:http pattern="/service/actuator/health" security="none" />
+ <security:http pattern="/service/actuator/health/liveness"
security="none" />
<security:http pattern="/service/metrics/**" security="none" />
<security:http disable-url-rewriting="true" use-expressions="true"
create-session="always"
entry-point-ref="authenticationProcessingFilterEntryPoint">
<csrf disabled="true"/>
diff --git
a/security-admin/src/test/java/org/apache/ranger/biz/TestSessionMgr.java
b/security-admin/src/test/java/org/apache/ranger/biz/TestSessionMgr.java
index 6b3baaa58..68033907b 100644
--- a/security-admin/src/test/java/org/apache/ranger/biz/TestSessionMgr.java
+++ b/security-admin/src/test/java/org/apache/ranger/biz/TestSessionMgr.java
@@ -122,6 +122,9 @@ public class TestSessionMgr {
@Mock
StringUtil stringUtil;
+ @Mock
+ RangerBizUtil bizUtil;
+
@AfterEach
public void tearDownSuperUserConfig() {
PropertiesUtil.getPropertiesMap().remove(RangerConstants.RANGER_ADMIN_SUPER_USERS);
@@ -129,6 +132,40 @@ public void tearDownSuperUserConfig() {
RangerSuperUserConfig.resetForTests();
}
+ @Test
+ public void testStoreAuthSessionSkipsHealthCheckUser() {
+ XXAuthSession authSession = new XXAuthSession();
+ authSession.setLoginId(RangerBizUtil.HEALTHCHECK_USERNAME);
+
+
when(bizUtil.isHealthCheckUser(RangerBizUtil.HEALTHCHECK_USERNAME)).thenReturn(true);
+
+ XXAuthSession ret = sessionMgr.storeAuthSession(authSession);
+
+ // No x_auth_sess record is created for the health-check user, and the
DAO is never touched.
+ assertEquals(authSession, ret);
+ assertNull(ret.getId());
+ verify(daoManager, never()).getXXAuthSession();
+ }
+
+ @Test
+ public void testStoreAuthSessionPersistsRegularUser() {
+ XXAuthSession authSession = new XXAuthSession();
+ authSession.setLoginId("regularUser");
+
+ when(bizUtil.isHealthCheckUser("regularUser")).thenReturn(false);
+
+ XXAuthSession created = new XXAuthSession();
+ created.setId(7L);
+
+ XXAuthSessionDao authDao = mock(XXAuthSessionDao.class);
+ when(daoManager.getXXAuthSession()).thenReturn(authDao);
+ when(authDao.create(authSession)).thenReturn(created);
+
+ XXAuthSession ret = sessionMgr.storeAuthSession(authSession);
+
+ assertEquals(created, ret);
+ }
+
@Test
public void testProcessSuccessLogin_ExistingValidSession() {
// Prepare existing user session in RangerContextHolder
diff --git
a/security-admin/src/test/java/org/apache/ranger/rest/TestRangerHealthREST.java
b/security-admin/src/test/java/org/apache/ranger/rest/TestRangerHealthREST.java
index ddad9331d..de9aa9082 100644
---
a/security-admin/src/test/java/org/apache/ranger/rest/TestRangerHealthREST.java
+++
b/security-admin/src/test/java/org/apache/ranger/rest/TestRangerHealthREST.java
@@ -18,6 +18,7 @@
package org.apache.ranger.rest;
import org.apache.ranger.biz.RangerBizUtil;
+import org.apache.ranger.common.RESTErrorUtil;
import org.apache.ranger.plugin.model.RangerServerHealth;
import org.apache.ranger.util.RangerServerHealthUtil;
import org.junit.jupiter.api.Assertions;
@@ -30,11 +31,18 @@
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.WebApplicationException;
+
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
+import static
org.apache.ranger.plugin.model.RangerServerHealth.RangerServerStatus.INITIALIZATION_FAILURE;
import static
org.apache.ranger.plugin.model.RangerServerHealth.RangerServerStatus.UP;
+import static org.mockito.ArgumentMatchers.isNull;
@ExtendWith(MockitoExtension.class)
@TestMethodOrder(MethodOrderer.MethodName.class)
@@ -44,12 +52,14 @@ public class TestRangerHealthREST {
@InjectMocks
RangerHealthREST rangerHealthREST = new RangerHealthREST();
@Mock
- RangerBizUtil xaBizUtil;
+ RangerBizUtil bizUtil;
+ @Mock
+ RESTErrorUtil restErrorUtil;
@Test
public void testHealthCheckStatusAPI() {
String dbVersion = "23.2.0";
- Mockito.when(xaBizUtil.getDBVersion()).thenReturn(dbVersion);
+ Mockito.when(bizUtil.getDBVersion()).thenReturn(dbVersion);
Mockito.when(rangerServerHealthUtil.getRangerServerHealth(dbVersion)).thenReturn(createRangerServerHealth());
RangerServerHealth rangerServerHealth =
rangerHealthREST.getRangerServerHealth();
Assertions.assertEquals(UP, rangerServerHealth.getStatus(),
"RangerHealth.down()");
@@ -57,6 +67,65 @@ public void testHealthCheckStatusAPI() {
Assertions.assertEquals(2, ((Map<?, ?>)
rangerServerHealth.getDetails().get("components")).size(),
"RangerHealth.getDetails('component')");
}
+ @Test
+ public void testLivenessReturnsUp() {
+
Mockito.when(rangerServerHealthUtil.serviceUp()).thenReturn(RangerServerHealth.up().build());
+ RangerServerHealth health = rangerHealthREST.getRangerServerLiveness();
+ Assertions.assertEquals(UP, health.getStatus(), "liveness should
report UP");
+ }
+
+ @Test
+ public void testReadinessUpWhenServiceDefsExist() {
+
Mockito.when(rangerServerHealthUtil.resolveAuthenticatedLoginId()).thenReturn(RangerBizUtil.HEALTHCHECK_USERNAME);
+
Mockito.when(bizUtil.isHealthCheckUser(RangerBizUtil.HEALTHCHECK_USERNAME)).thenReturn(true);
+
Mockito.when(rangerServerHealthUtil.getServiceDefNames()).thenReturn(Arrays.asList("hdfs"));
+
Mockito.when(rangerServerHealthUtil.serviceUpWithAvailableServiceDefs(Arrays.asList("hdfs"))).thenReturn(RangerServerHealth.up().build());
+
+ RangerServerHealth health =
rangerHealthREST.getRangerServerReadiness();
+ Assertions.assertEquals(UP, health.getStatus(), "readiness should be
UP when service-defs exist");
+ Mockito.verify(rangerServerHealthUtil).getServiceDefNames();
+
Mockito.verify(rangerServerHealthUtil).serviceUpWithAvailableServiceDefs(Arrays.asList("hdfs"));
+ }
+
+ @Test
+ public void testReadinessInitFailureWhenNoServiceDefs() {
+
Mockito.when(rangerServerHealthUtil.resolveAuthenticatedLoginId()).thenReturn(RangerBizUtil.HEALTHCHECK_USERNAME);
+
Mockito.when(bizUtil.isHealthCheckUser(RangerBizUtil.HEALTHCHECK_USERNAME)).thenReturn(true);
+
Mockito.when(rangerServerHealthUtil.getServiceDefNames()).thenReturn(Collections.emptyList());
+
Mockito.when(rangerServerHealthUtil.serviceInitFailure()).thenReturn(RangerServerHealth.initFailure().build());
+
+ RangerServerHealth health =
rangerHealthREST.getRangerServerReadiness();
+ Assertions.assertEquals(INITIALIZATION_FAILURE, health.getStatus(),
+ "readiness should report INITIALIZATION_FAILURE when no
service-defs exist");
+ Mockito.verify(rangerServerHealthUtil).getServiceDefNames();
+ }
+
+ @Test
+ public void testReadinessForbiddenForOtherUser() {
+
Mockito.when(rangerServerHealthUtil.resolveAuthenticatedLoginId()).thenReturn("admin");
+ Mockito.when(bizUtil.isHealthCheckUser("admin")).thenReturn(false);
+
Mockito.when(restErrorUtil.createRESTException(Mockito.eq(HttpServletResponse.SC_FORBIDDEN),
+ Mockito.eq("Only the healthcheck user may query service-def
names via this path."),
+ Mockito.eq(true))).thenReturn(new
WebApplicationException(HttpServletResponse.SC_FORBIDDEN));
+
+ Assertions.assertThrows(WebApplicationException.class, () ->
rangerHealthREST.getRangerServerReadiness());
+
+ Mockito.verify(rangerServerHealthUtil,
Mockito.never()).getServiceDefNames();
+ }
+
+ @Test
+ public void testReadinessForbiddenWhenUnauthenticated() {
+
Mockito.when(rangerServerHealthUtil.resolveAuthenticatedLoginId()).thenReturn(null);
+ Mockito.when(bizUtil.isHealthCheckUser(isNull())).thenReturn(false);
+
Mockito.when(restErrorUtil.createRESTException(Mockito.eq(HttpServletResponse.SC_FORBIDDEN),
+ Mockito.eq("Only the healthcheck user may query service-def
names via this path."),
+ Mockito.eq(true))).thenReturn(new
WebApplicationException(HttpServletResponse.SC_FORBIDDEN));
+
+ Assertions.assertThrows(WebApplicationException.class, () ->
rangerHealthREST.getRangerServerReadiness());
+
+ Mockito.verify(rangerServerHealthUtil,
Mockito.never()).getServiceDefNames();
+ }
+
private RangerServerHealth createRangerServerHealth() {
Map<String, Object> componentsMap = new HashMap<>();
Map<String, Object> dbMap = new LinkedHashMap<>();
diff --git
a/security-admin/src/test/java/org/apache/ranger/util/TestRangerServerHealthUtil.java
b/security-admin/src/test/java/org/apache/ranger/util/TestRangerServerHealthUtil.java
index 09914cf35..dda6559c2 100644
---
a/security-admin/src/test/java/org/apache/ranger/util/TestRangerServerHealthUtil.java
+++
b/security-admin/src/test/java/org/apache/ranger/util/TestRangerServerHealthUtil.java
@@ -17,18 +17,38 @@
package org.apache.ranger.util;
+import org.apache.ranger.biz.RangerBizUtil;
+import org.apache.ranger.entity.XXAuthSession;
import org.apache.ranger.plugin.model.RangerServerHealth;
+import org.apache.ranger.security.web.filter.RangerAuthenticationToken;
+import org.apache.ranger.service.RangerServiceDefService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import
org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.User;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.Map;
import static
org.apache.ranger.plugin.model.RangerServerHealth.RangerServerStatus.DOWN;
+import static
org.apache.ranger.plugin.model.RangerServerHealth.RangerServerStatus.INITIALIZATION_FAILURE;
+@ExtendWith(MockitoExtension.class)
public class TestRangerServerHealthUtil {
@InjectMocks
- RangerServerHealthUtil rangerServerHealthUtil = new
RangerServerHealthUtil();
+ RangerServerHealthUtil rangerServerHealthUtil = new
RangerServerHealthUtil();
+ @Mock
+ RangerBizUtil bizUtil;
+ @Mock
+ RangerServiceDefService serviceDefService;
@Test
public void testGetRangerServerHealth() {
@@ -37,4 +57,67 @@ public void testGetRangerServerHealth() {
Assertions.assertEquals(1, rangerServerHealth.getDetails().size(),
"RangerHealth.getDetails()");
Assertions.assertEquals(1, ((Map<?, ?>)
rangerServerHealth.getDetails().get("components")).size(),
"RangerHealth.getDetails('component')");
}
+
+ @Test
+ public void testServiceUpWithAvailableServiceDefNames() {
+ RangerServerHealth health =
rangerServerHealthUtil.serviceUpWithAvailableServiceDefs(Arrays.asList("hdfs",
"hive"));
+ Assertions.assertEquals(RangerServerHealth.RangerServerStatus.UP,
health.getStatus());
+ Assertions.assertEquals(Arrays.asList("hdfs", "hive"),
+ ((Map<?, ?>)
health.getDetails().get("components")).get("service-defs"));
+ }
+
+ @Test
+ public void testServiceInitFailure() {
+ RangerServerHealth health =
rangerServerHealthUtil.serviceInitFailure();
+ Assertions.assertEquals(INITIALIZATION_FAILURE, health.getStatus());
+ }
+
+ @Test
+ public void testGetServiceDefNames() {
+ List<String> expected = Arrays.asList("hdfs", "hive");
+
Mockito.when(serviceDefService.getAllServiceDefNames()).thenReturn(expected);
+
+ List<String> ret = rangerServerHealthUtil.getServiceDefNames();
+
+ Assertions.assertEquals(expected, ret);
+ Mockito.verify(serviceDefService).getAllServiceDefNames();
+ }
+
+ @Test
+ public void testResolveAuthenticatedLoginId_FromUserSession() {
+
Mockito.when(bizUtil.getCurrentUserLoginId()).thenReturn(RangerBizUtil.HEALTHCHECK_USERNAME);
+
+ Assertions.assertEquals(RangerBizUtil.HEALTHCHECK_USERNAME,
rangerServerHealthUtil.resolveAuthenticatedLoginId());
+ }
+
+ @Test
+ public void testResolveAuthenticatedLoginId_FromTrustedProxyToken() {
+ Mockito.when(bizUtil.getCurrentUserLoginId()).thenReturn(null);
+ SecurityContextHolder.getContext().setAuthentication(
+ trustedProxyAuth(RangerBizUtil.HEALTHCHECK_USERNAME));
+
+ try {
+ Assertions.assertEquals(RangerBizUtil.HEALTHCHECK_USERNAME,
rangerServerHealthUtil.resolveAuthenticatedLoginId());
+ } finally {
+ SecurityContextHolder.clearContext();
+ }
+ }
+
+ @Test
+ public void testResolveAuthenticatedLoginId_Unauthenticated() {
+ Mockito.when(bizUtil.getCurrentUserLoginId()).thenReturn(null);
+ SecurityContextHolder.getContext().setAuthentication(
+ new
UsernamePasswordAuthenticationToken(RangerBizUtil.HEALTHCHECK_USERNAME, "",
Collections.emptyList()));
+
+ try {
+
Assertions.assertNull(rangerServerHealthUtil.resolveAuthenticatedLoginId());
+ } finally {
+ SecurityContextHolder.clearContext();
+ }
+ }
+
+ private RangerAuthenticationToken trustedProxyAuth(String username) {
+ return new RangerAuthenticationToken(new User(username, "",
Collections.emptyList()),
+ Collections.emptyList(),
XXAuthSession.AUTH_TYPE_TRUSTED_PROXY);
+ }
}