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);
+    }
 }

Reply via email to