From 0f73972aa59b259045c8ef5ba4f8c15ea6ffddad Mon Sep 17 00:00:00 2001 From: Vyom Mani Tiwari Date: Mon, 23 Mar 2026 12:41:59 +0530 Subject: [PATCH 1/6] initial SAML implementation --- pom.xml | 5 ++ security-admin/pom.xml | 61 +++++++++++++- .../ranger/common/RangerCommonEnums.java | 1 + .../handler/RangerAuthenticationProvider.java | 64 ++++++++++++++- ...gerDelegatingAuthenticationEntryPoint.java | 58 ++++++++++++++ .../saml/RangerSamlRegistrationFactory.java | 44 ++++++++++ .../resources/conf.dist/ranger-admin-site.xml | 44 +++++++++- .../conf.dist/security-applicationContext.xml | 80 +++++++++++++++++-- security-admin/src/main/webapp/login.jsp | 9 +++ 9 files changed, 358 insertions(+), 8 deletions(-) create mode 100644 security-admin/src/main/java/org/apache/ranger/security/web/authentication/RangerDelegatingAuthenticationEntryPoint.java create mode 100644 security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java diff --git a/pom.xml b/pom.xml index 2c7ad5dc61..33f44495a9 100755 --- a/pom.xml +++ b/pom.xml @@ -766,6 +766,11 @@ https://issues.apache.org/jira/browse/ranger + + shibboleth-releases + Shibboleth Release Repository + https://build.shibboleth.net/maven/releases/ + jetbrains-pty4j jetbrains-intellij-dependencies diff --git a/security-admin/pom.xml b/security-admin/pom.xml index 3a30a36c4f..522ff28728 100644 --- a/security-admin/pom.xml +++ b/security-admin/pom.xml @@ -247,6 +247,12 @@ netty-all ${netty-all.version} + + + jakarta.xml.bind + jakarta.xml.bind-api + 2.3.3 + javax.servlet javax.servlet-api @@ -266,6 +272,13 @@ json-smart ${jsonsmart.version} + + + + net.shibboleth.utilities + java-support + 8.4.0 + org.antlr antlr-runtime @@ -686,6 +699,42 @@ cglib ${cglib.version} + + + org.opensaml + opensaml-core + 4.3.0 + + + org.opensaml + opensaml-saml-api + 4.3.0 + + + org.opensaml + opensaml-saml-impl + 4.3.0 + + + org.opensaml + opensaml-security-api + 4.3.0 + + + org.opensaml + opensaml-security-impl + 4.3.0 + + + org.opensaml + opensaml-xmlsec-api + 4.3.0 + + + org.opensaml + opensaml-xmlsec-impl + 4.3.0 + org.springframework spring-aop @@ -778,6 +827,11 @@ spring-security-ldap ${springframework.security.version} + + org.springframework.security + spring-security-saml2-service-provider + ${springframework.security.version} + org.springframework.security spring-security-web @@ -788,7 +842,12 @@ jsr250-api provided - + + com.sun.xml.bind + jaxb-impl + 2.3.9 + runtime + org.apache.ranger diff --git a/security-admin/src/main/java/org/apache/ranger/common/RangerCommonEnums.java b/security-admin/src/main/java/org/apache/ranger/common/RangerCommonEnums.java index 2aefb8aaca..9e03579e07 100644 --- a/security-admin/src/main/java/org/apache/ranger/common/RangerCommonEnums.java +++ b/security-admin/src/main/java/org/apache/ranger/common/RangerCommonEnums.java @@ -468,6 +468,7 @@ public class RangerCommonEnums { public static final int USER_UNIX = 4; public static final int USER_REPO = 5; public static final int USER_FEDERATED = 6; + public static final int USER_SAML = 7; public static final int GROUP_INTERNAL = 0; public static final int GROUP_EXTERNAL = 1; diff --git a/security-admin/src/main/java/org/apache/ranger/security/handler/RangerAuthenticationProvider.java b/security-admin/src/main/java/org/apache/ranger/security/handler/RangerAuthenticationProvider.java index 884a03d05c..9d2b06d68d 100644 --- a/security-admin/src/main/java/org/apache/ranger/security/handler/RangerAuthenticationProvider.java +++ b/security-admin/src/main/java/org/apache/ranger/security/handler/RangerAuthenticationProvider.java @@ -55,6 +55,8 @@ import org.springframework.security.ldap.search.FilterBasedLdapUserSearch; import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator; import org.springframework.security.provisioning.JdbcUserDetailsManager; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import javax.security.auth.login.AppConfigurationEntry; import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag; @@ -144,6 +146,12 @@ public Authentication authenticate(Authentication authentication) throws Authent } else if ("PAM".equalsIgnoreCase(rangerAuthenticationMethod)) { authentication = getPamAuthentication(authentication); + if (authentication != null && authentication.isAuthenticated()) { + return authentication; + } + } else if ("SAML".equalsIgnoreCase(rangerAuthenticationMethod)) { + authentication = getSAMLAuthentication(authentication); + if (authentication != null && authentication.isAuthenticated()) { return authentication; } @@ -215,7 +223,7 @@ public Authentication authenticate(Authentication authentication) throws Authent @Override public boolean supports(Class authentication) { - return authentication.equals(UsernamePasswordAuthenticationToken.class); + return authentication.equals(UsernamePasswordAuthenticationToken.class) || Saml2Authentication.class.isAssignableFrom(authentication); } public Authentication getADAuthentication(Authentication authentication) { @@ -366,6 +374,60 @@ public Authentication getUnixAuthentication(Authentication authentication) { return authentication; } + private Authentication getSAMLAuthentication(Authentication authentication) { + try { + if (!(authentication instanceof Saml2Authentication)) { + logger.debug("Not a SAML authentication, skipping"); + return null; + } + + String username = authentication.getName(); + if (StringUtil.isEmpty(username)) { + throw new BadCredentialsException("No username in SAML authentication"); + } + + Object principal = authentication.getPrincipal(); + if (!(principal instanceof Saml2AuthenticatedPrincipal)) { + logger.warn("Expected Saml2AuthenticatedPrincipal but got {}", principal.getClass().getName()); + return null; + } + + Saml2AuthenticatedPrincipal samlPrincipal = (Saml2AuthenticatedPrincipal) principal; + + // Get username from attribute or NameID + String usernameAttr = PropertiesUtil.getProperty("ranger.saml.attribute.username", "NameID"); + if (!"NameID".equalsIgnoreCase(usernameAttr)) { + String attrValue = samlPrincipal.getFirstAttribute(usernameAttr); + if (!StringUtil.isEmpty(attrValue)) { + username = attrValue; + } + } + + String email = samlPrincipal.getFirstAttribute("email"); + String groupAttrName = PropertiesUtil.getProperty("ranger.saml.attribute.role", "groups"); + List groups = samlPrincipal.getAttribute(groupAttrName); + String rangerSamlDefaultRole = PropertiesUtil.getProperty("ranger.saml.default.role", "ROLE_USER"); + + logger.info("SAML authenticated user: {}, email: {}, groups: {}", username, email, groups); + + List grantedAuths = getAuthorities(username); + if (grantedAuths.isEmpty()) { + logger.info("SAML user '{}' not found in Ranger DB. " + "Assigning session default role: {}", username, rangerSamlDefaultRole); + grantedAuths.add(new SimpleGrantedAuthority(rangerSamlDefaultRole)); + } + final UserDetails userDetails = new User(username, "", grantedAuths); + UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userDetails, "", grantedAuths); + result.setDetails(authentication.getDetails()); + logger.info("SAML authentication successful for user: {} with authorities: {}", username, grantedAuths); + return result; + } catch (BadCredentialsException e) { + throw e; + } catch (Exception e) { + logger.error("SAML authentication processing failed", e); + throw new BadCredentialsException("SAML processing error", e); + } + } + public String getRangerAuthenticationMethod() { return rangerAuthenticationMethod; } diff --git a/security-admin/src/main/java/org/apache/ranger/security/web/authentication/RangerDelegatingAuthenticationEntryPoint.java b/security-admin/src/main/java/org/apache/ranger/security/web/authentication/RangerDelegatingAuthenticationEntryPoint.java new file mode 100644 index 0000000000..48eb3ca809 --- /dev/null +++ b/security-admin/src/main/java/org/apache/ranger/security/web/authentication/RangerDelegatingAuthenticationEntryPoint.java @@ -0,0 +1,58 @@ +/* + * 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.ranger.security.web.authentication; + +import org.apache.ranger.common.PropertiesUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public class RangerDelegatingAuthenticationEntryPoint implements AuthenticationEntryPoint { + private static final Logger logger = LoggerFactory.getLogger(RangerDelegatingAuthenticationEntryPoint.class); + + private static final String AUTH_METHOD_SAML = "SAML"; + + private final LoginUrlAuthenticationEntryPoint samlEntryPoint; + private final AuthenticationEntryPoint defaultEntryPoint; + + public RangerDelegatingAuthenticationEntryPoint(String samlLoginUrl, AuthenticationEntryPoint defaultEntryPoint) { + this.samlEntryPoint = new LoginUrlAuthenticationEntryPoint(samlLoginUrl); + this.defaultEntryPoint = defaultEntryPoint; + } + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException, ServletException { + String authMethod = PropertiesUtil.getProperty("ranger.authentication.method", "NONE"); + logger.debug("RangerDelegatingAuthenticationEntryPoint.commence() authMethod={}", authMethod); + if (AUTH_METHOD_SAML.equalsIgnoreCase(authMethod)) { + samlEntryPoint.commence(request, response, authException); + } else { + defaultEntryPoint.commence(request, response, authException); + } + } +} diff --git a/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java b/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java new file mode 100644 index 0000000000..805accc2a1 --- /dev/null +++ b/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java @@ -0,0 +1,44 @@ +/* + * 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.ranger.security.web.saml; + +import org.springframework.core.io.FileSystemResource; +import org.springframework.security.converter.RsaKeyConverters; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; + +import java.io.FileInputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; + +public class RangerSamlRegistrationFactory { + private RangerSamlRegistrationFactory(){} + + public static RelyingPartyRegistration buildWithSigningCredential(RelyingPartyRegistration.Builder builder, String privateKeyPath, String certPath) throws Exception { + RSAPrivateKey privateKey = RsaKeyConverters.pkcs8().convert(new FileSystemResource(privateKeyPath).getInputStream()); + X509Certificate certificate; + try (FileInputStream fis = new FileInputStream(certPath)) { + certificate = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(fis); + } + Saml2X509Credential signingCredential = Saml2X509Credential.signing(privateKey, certificate); + return builder.signingX509Credentials(c -> c.add(signingCredential)).build(); + } +} 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 d1fccc27d7..30f9944d00 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 @@ -109,7 +109,7 @@ ranger.authentication.method - NONE + SAML @@ -453,4 +453,46 @@ xasecure.audit.jaas.Client.option.principal + + ranger.saml.attribute.username + NameID + + + ranger.saml.attribute.role + groups + + + ranger.saml.default.role + ROLE_USER + + + ranger.saml.admin.group + ranger-admins + + + + ranger.saml.entity.id + ranger-saml + SAML SP Entity ID — must match the Client ID registered in the IdP (e.g. Keycloak) + + + ranger.saml.idp.metadata.location + file:/opt/ranger/ranger-3.0.0-SNAPSHOT-admin/conf/saml/keycloak-IdP-metadata.xml + IdP metadata file location (file: or http: URI) + + + ranger.saml.sp.key + /opt/ranger/ranger-3.0.0-SNAPSHOT-admin/conf/saml/ranger-sp-clean.key + Absolute path to the SP private key (PKCS8 PEM) used to sign AuthnRequests + + + ranger.saml.sp.cert + /opt/ranger/ranger-3.0.0-SNAPSHOT-admin/conf/saml/ranger-sp.crt + Absolute path to the SP public certificate (X.509 PEM) + + + ranger.saml.success.url + /index.html + URL to redirect to after successful SAML authentication + 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 bbb94a3a43..a193990320 100644 --- a/security-admin/src/main/resources/conf.dist/security-applicationContext.xml +++ b/security-admin/src/main/resources/conf.dist/security-applicationContext.xml @@ -52,7 +52,9 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> - + + + @@ -63,20 +65,23 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> + + + + - + - @@ -85,7 +90,11 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> - + + + + @@ -104,6 +113,12 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> + + + + + @@ -135,9 +150,64 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/security-admin/src/main/webapp/login.jsp b/security-admin/src/main/webapp/login.jsp index b25cb78ad0..95785563f5 100644 --- a/security-admin/src/main/webapp/login.jsp +++ b/security-admin/src/main/webapp/login.jsp @@ -14,6 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. --> +<%@ page import="org.apache.ranger.common.PropertiesUtil" %> +<% + String authMethod = PropertiesUtil.getProperty("ranger.authentication.method", "NONE"); + if ("SAML".equalsIgnoreCase(authMethod)) { + String entityId = PropertiesUtil.getProperty("ranger.saml.entity.id", "ranger-saml"); + response.sendRedirect(request.getContextPath() + "/saml2/authenticate/" + entityId); + return; + } +%> From 8c9dba893cbf2ec93f1b3c4073c12bc6527e45ce Mon Sep 17 00:00:00 2001 From: Vyom Mani Tiwari Date: Thu, 26 Mar 2026 12:52:38 +0530 Subject: [PATCH 2/6] implemented the logout for saml as well --- .../org/apache/ranger/biz/SessionMgr.java | 6 +- .../apache/ranger/entity/XXAuthSession.java | 7 ++- .../java/org/apache/ranger/rest/UserREST.java | 1 + .../RangerDelegatingLogoutSuccessHandler.java | 58 +++++++++++++++++++ .../RangerSecurityContextFormationFilter.java | 4 +- .../saml/RangerSamlRegistrationFactory.java | 8 ++- .../apache/ranger/util/RangerEnumUtil.java | 8 +++ .../conf.dist/security-applicationContext.xml | 52 ++++++++++++++++- security-admin/src/main/webapp/login.jsp | 6 +- .../webapp/react-webapp/src/utils/XAUtils.js | 11 ++++ .../src/views/SideBar/SideBarBody.jsx | 11 ++++ 11 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 security-admin/src/main/java/org/apache/ranger/security/web/authentication/RangerDelegatingLogoutSuccessHandler.java 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 107c702477..9205a610b8 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 @@ -511,9 +511,11 @@ private void getSSOSpnegoAuthCheckForAPI(String currentLoginId, HttpServletReque 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); + String authMethod = PropertiesUtil.getProperty("ranger.authentication.method", "NONE"); + boolean samlEnabled = "SAML".equalsIgnoreCase(authMethod); - 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); + if (gjUser == null && ((request.getAttribute("spnegoEnabled") != null && (boolean) request.getAttribute("spnegoEnabled")) || (ssoEnabled) || (samlEnabled))) { + logger.debug("User : {} doesn't exist in Ranger DB So creating user as it's SSO or Spnego or saml authenticated", currentLoginId); xUserMgr.createServiceConfigUser(currentLoginId); } diff --git a/security-admin/src/main/java/org/apache/ranger/entity/XXAuthSession.java b/security-admin/src/main/java/org/apache/ranger/entity/XXAuthSession.java index 00132e7f0e..abe2a3837e 100644 --- a/security-admin/src/main/java/org/apache/ranger/entity/XXAuthSession.java +++ b/security-admin/src/main/java/org/apache/ranger/entity/XXAuthSession.java @@ -116,10 +116,15 @@ public class XXAuthSession extends XXDBBase implements java.io.Serializable { */ public static final int AUTH_TYPE_TRUSTED_PROXY = 4; + /** + * AUTH_TYPE_SAML is an element of enum AuthType. Its value is "AUTH_TYPE_SAML". + */ + public static final int AUTH_TYPE_SAML = 5; + /** * Max value for enum AuthType_MAX */ - public static final int AuthType_MAX = 4; + public static final int AuthType_MAX = 5; @Id @SequenceGenerator(name = "X_AUTH_SESS_SEQ", sequenceName = "X_AUTH_SESS_SEQ", allocationSize = 1) diff --git a/security-admin/src/main/java/org/apache/ranger/rest/UserREST.java b/security-admin/src/main/java/org/apache/ranger/rest/UserREST.java index 8f8692afd5..082e7020f5 100644 --- a/security-admin/src/main/java/org/apache/ranger/rest/UserREST.java +++ b/security-admin/src/main/java/org/apache/ranger/rest/UserREST.java @@ -274,6 +274,7 @@ public VXPortalUser getUserProfile(@Context HttpServletRequest request) { long inactivityTimeout = PropertiesUtil.getLongProperty("ranger.service.inactivity.timeout", 15 * 60); configProperties.put("inactivityTimeout", Long.toString(inactivityTimeout)); + configProperties.put("authenticationMethod", PropertiesUtil.getProperty("ranger.authentication.method", "NONE")); VXPortalUser userProfile = userManager.getUserProfileByLoginId(); diff --git a/security-admin/src/main/java/org/apache/ranger/security/web/authentication/RangerDelegatingLogoutSuccessHandler.java b/security-admin/src/main/java/org/apache/ranger/security/web/authentication/RangerDelegatingLogoutSuccessHandler.java new file mode 100644 index 0000000000..b6e46ea2ce --- /dev/null +++ b/security-admin/src/main/java/org/apache/ranger/security/web/authentication/RangerDelegatingLogoutSuccessHandler.java @@ -0,0 +1,58 @@ +/* + * 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.ranger.security.web.authentication; + +import org.apache.ranger.common.PropertiesUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2RelyingPartyInitiatedLogoutSuccessHandler; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public class RangerDelegatingLogoutSuccessHandler implements LogoutSuccessHandler { + private static final Logger logger = LoggerFactory.getLogger(RangerDelegatingLogoutSuccessHandler.class); + private static final String AUTH_METHOD_SAML = "SAML"; + + private final Saml2RelyingPartyInitiatedLogoutSuccessHandler samlLogoutSuccessHandler; + private final LogoutSuccessHandler defaultLogoutSuccessHandler; + + public RangerDelegatingLogoutSuccessHandler(Saml2RelyingPartyInitiatedLogoutSuccessHandler samlLogoutSuccessHandler, + LogoutSuccessHandler defaultLogoutSuccessHandler) { + this.samlLogoutSuccessHandler = samlLogoutSuccessHandler; + this.defaultLogoutSuccessHandler = defaultLogoutSuccessHandler; + } + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + String authMethod = PropertiesUtil.getProperty("ranger.authentication.method", "NONE"); + logger.debug("RangerDelegatingLogoutSuccessHandler.onLogoutSuccess() authMethod={}", authMethod); + if (AUTH_METHOD_SAML.equalsIgnoreCase(authMethod)) { + samlLogoutSuccessHandler.onLogoutSuccess(request, response, authentication); + } else { + defaultLogoutSuccessHandler.onLogoutSuccess(request, response, authentication); + } + } +} diff --git a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerSecurityContextFormationFilter.java b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerSecurityContextFormationFilter.java index cf03c6cbdb..b244741bbf 100644 --- a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerSecurityContextFormationFilter.java +++ b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerSecurityContextFormationFilter.java @@ -174,7 +174,7 @@ private int getAuthType(Authentication auth, HttpServletRequest request) { Object ssoEnabledObj = request.getAttribute("ssoEnabled"); boolean ssoEnabled = ssoEnabledObj != null ? Boolean.parseBoolean(String.valueOf(ssoEnabledObj)) : PropertiesUtil.getBooleanProperty("ranger.sso.enabled", false); - + String authMethod = PropertiesUtil.getProperty("ranger.authentication.method", "NONE"); if (ssoEnabled) { return XXAuthSession.AUTH_TYPE_SSO; } else if (request.getAttribute("spnegoEnabled") != null && Boolean.parseBoolean(String.valueOf(request.getAttribute("spnegoEnabled")))) { @@ -185,6 +185,8 @@ private int getAuthType(Authentication auth, HttpServletRequest request) { } else { return XXAuthSession.AUTH_TYPE_KERBEROS; } + } else if ("SAML".equalsIgnoreCase(authMethod)) { + return XXAuthSession.AUTH_TYPE_SAML; } return XXAuthSession.AUTH_TYPE_PASSWORD; diff --git a/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java b/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java index 805accc2a1..db622f277f 100644 --- a/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java +++ b/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java @@ -23,6 +23,7 @@ import org.springframework.security.converter.RsaKeyConverters; import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; import java.io.FileInputStream; import java.security.cert.CertificateFactory; @@ -39,6 +40,11 @@ public static RelyingPartyRegistration buildWithSigningCredential(RelyingPartyRe certificate = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(fis); } Saml2X509Credential signingCredential = Saml2X509Credential.signing(privateKey, certificate); - return builder.signingX509Credentials(c -> c.add(signingCredential)).build(); + return builder + .signingX509Credentials(c -> c.add(signingCredential)) + .singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo") + .singleLogoutServiceResponseLocation("{baseUrl}/logout/saml2/slo") + .singleLogoutServiceBinding(Saml2MessageBinding.REDIRECT) + .build(); } } diff --git a/security-admin/src/main/java/org/apache/ranger/util/RangerEnumUtil.java b/security-admin/src/main/java/org/apache/ranger/util/RangerEnumUtil.java index 6f00e7a835..46f370d1cb 100644 --- a/security-admin/src/main/java/org/apache/ranger/util/RangerEnumUtil.java +++ b/security-admin/src/main/java/org/apache/ranger/util/RangerEnumUtil.java @@ -1957,6 +1957,14 @@ protected void init() { vElement.setRbKey("xa.enum.AuthType.AUTH_TYPE_TRUSTED_PROXY"); vElement.setEnumName(vEnum.getEnumName()); + vEnum.getElementList().add(vElement); + vElement = new VEnumElement(); + vElement.setElementName("AUTH_TYPE_SAML"); + vElement.setElementValue(5); + vElement.setElementLabel("SAML"); + vElement.setRbKey("xa.enum.AuthType.AUTH_TYPE_SAML"); + vElement.setEnumName(vEnum.getEnumName()); + vEnum.getElementList().add(vElement); /////////////////////////////////// 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 a193990320..04b1fec59a 100644 --- a/security-admin/src/main/resources/conf.dist/security-applicationContext.xml +++ b/security-admin/src/main/resources/conf.dist/security-applicationContext.xml @@ -67,6 +67,7 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> + @@ -75,11 +76,13 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> + + - + @@ -122,6 +125,47 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -200,7 +244,7 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> - + @@ -210,4 +254,8 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> class="org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter"> + + + diff --git a/security-admin/src/main/webapp/login.jsp b/security-admin/src/main/webapp/login.jsp index 95785563f5..05a864c0ab 100644 --- a/security-admin/src/main/webapp/login.jsp +++ b/security-admin/src/main/webapp/login.jsp @@ -17,7 +17,11 @@ <%@ page import="org.apache.ranger.common.PropertiesUtil" %> <% String authMethod = PropertiesUtil.getProperty("ranger.authentication.method", "NONE"); - if ("SAML".equalsIgnoreCase(authMethod)) { + // Only auto-redirect to IdP if we are NOT coming back from a logout. + // After a successful logout, samlPostLogoutRedirectHandler sends the user here + // with ?loggedOut=true so we show the login page instead of looping back to the IdP. + String loggedOut = request.getParameter("loggedOut"); + if ("SAML".equalsIgnoreCase(authMethod) && !"true".equals(loggedOut)) { String entityId = PropertiesUtil.getProperty("ranger.saml.entity.id", "ranger-saml"); response.sendRedirect(request.getContextPath() + "/saml2/authenticate/" + entityId); return; diff --git a/security-admin/src/main/webapp/react-webapp/src/utils/XAUtils.js b/security-admin/src/main/webapp/react-webapp/src/utils/XAUtils.js index 0f9f66b589..5240556fa9 100644 --- a/security-admin/src/main/webapp/react-webapp/src/utils/XAUtils.js +++ b/security-admin/src/main/webapp/react-webapp/src/utils/XAUtils.js @@ -1420,6 +1420,17 @@ export const updateTagActive = (isTagView) => { export const handleLogout = async (checkKnoxSSOVal, navigate) => { try { + // For SAML, we must do a full browser navigation to /logout so Spring Security + // can perform the SLO redirect chain (Ranger -> IdP -> back to Ranger). + // An AJAX call cannot follow the cross-domain SLO redirect, which causes the + // IdP session to remain active and results in an automatic re-login loop. + const userProfile = getUserProfile(); + const authMethod = userProfile?.configProperties?.authenticationMethod; + if (authMethod?.toUpperCase() === "SAML") { + window.location.replace("logout"); + return; + } + await fetchApi({ url: "logout", baseURL: "", diff --git a/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBarBody.jsx b/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBarBody.jsx index a8e87f8ab3..351be2af2f 100644 --- a/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBarBody.jsx +++ b/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBarBody.jsx @@ -227,6 +227,17 @@ export const SideBarBody = (props) => { const handleLogout = async (checkKnoxSSOVal) => { try { + // For SAML, we must do a full browser navigation to /logout so Spring Security + // can perform the SLO redirect chain (Ranger -> IdP -> back to Ranger). + // An AJAX call cannot follow the cross-domain SLO redirect, which causes the + // IdP session to remain active and results in an automatic re-login loop. + const currentUserProfile = getUserProfile(); + const authMethod = currentUserProfile?.configProperties?.authenticationMethod; + if (authMethod?.toUpperCase() === "SAML") { + window.location.replace("logout"); + return; + } + await fetchApi({ url: "logout", baseURL: "", From 9a7fe46dae931fddd9565a6a25d73516a09f58d6 Mon Sep 17 00:00:00 2001 From: Vyom Mani Tiwari Date: Sat, 28 Mar 2026 19:44:32 +0530 Subject: [PATCH 3/6] fixed the logout issue and provide support for jdk8 as well --- .../RangerApplicationContextInitializer.java | 117 ++++++ .../saml/RangerSamlRegistrationFactory.java | 19 +- .../resources/conf.dist/ranger-admin-site.xml | 8 +- .../conf.dist/security-applicationContext.xml | 366 ++++++++++-------- .../src/main/webapp/WEB-INF/web.xml | 4 + .../src/views/SideBar/SideBarBody.jsx | 4 +- 6 files changed, 358 insertions(+), 160 deletions(-) create mode 100644 security-admin/src/main/java/org/apache/ranger/security/context/RangerApplicationContextInitializer.java diff --git a/security-admin/src/main/java/org/apache/ranger/security/context/RangerApplicationContextInitializer.java b/security-admin/src/main/java/org/apache/ranger/security/context/RangerApplicationContextInitializer.java new file mode 100644 index 0000000000..df344c3a0f --- /dev/null +++ b/security-admin/src/main/java/org/apache/ranger/security/context/RangerApplicationContextInitializer.java @@ -0,0 +1,117 @@ +/* + * 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.ranger.security.context; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilderFactory; + +import java.io.InputStream; + +/** + * Activates the "saml" Spring profile when ranger.authentication.method=SAML. + * This ensures OpenSAML beans are only instantiated when SAML is configured, + * allowing non-SAML deployments to run safely on JDK 8. + *

+ * IMPORTANT: We cannot use PropertiesUtil here because it is populated by a + * BeanFactoryPostProcessor that runs after ApplicationContextInitializer. + */ +public class RangerApplicationContextInitializer implements ApplicationContextInitializer { + private static final Logger LOG = LoggerFactory.getLogger(RangerApplicationContextInitializer.class); + + private static final String SAML_AUTH_METHOD = "SAML"; + private static final String SAML_PROFILE = "saml"; + private static final String AUTH_METHOD_PROPERTY = "ranger.authentication.method"; + private static final String CONFIG_RESOURCE = "ranger-admin-site.xml"; + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + String authMethod = readAuthMethodFromConfigFile(); + + if (SAML_AUTH_METHOD.equalsIgnoreCase(authMethod)) { + // Critical safety check for JDK compatibility + try { + Class.forName("org.opensaml.saml.saml2.core.AuthnRequest", false, getClass().getClassLoader()); + } catch (ClassNotFoundException | UnsupportedClassVersionError e) { + throw new IllegalStateException( + "SAML 2.0 authentication is enabled (ranger.authentication.method=SAML), " + + "but it requires Java 11 or higher. OpenSAML 4.x is not compatible with Java 8. " + + "Please upgrade Ranger Admin JVM to Java 11+ or change the authentication method.", e); + } + LOG.info("RangerApplicationContextInitializer: activating '{}' Spring profile (ranger.authentication.method={})", SAML_PROFILE, authMethod); + applicationContext.getEnvironment().addActiveProfile(SAML_PROFILE); + } else { + LOG.info("RangerApplicationContextInitializer: SAML profile not activated (ranger.authentication.method={})", authMethod); + } + } + + /** + * Reads ranger.authentication.method directly from ranger-admin-site.xml on the + * classpath. This avoids the PropertiesUtil dependency, which is not yet + * initialised at ApplicationContextInitializer time. + * + * @return the configured authentication method, or {@code "NONE"} if the + * property is absent or the file cannot be read. + */ + private String readAuthMethodFromConfigFile() { + try (InputStream is = getClass().getClassLoader().getResourceAsStream(CONFIG_RESOURCE)) { + if (is == null) { + LOG.warn("RangerApplicationContextInitializer: {} not found on classpath; defaulting to NONE", CONFIG_RESOURCE); + return "NONE"; + } + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + // Harden against XXE + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + dbf.setExpandEntityReferences(false); + dbf.setNamespaceAware(false); + + Document doc = dbf.newDocumentBuilder().parse(is); + NodeList properties = doc.getElementsByTagName("property"); + + for (int i = 0; i < properties.getLength(); i++) { + Element property = (Element) properties.item(i); + NodeList names = property.getElementsByTagName("name"); + + if (names.getLength() > 0 && + AUTH_METHOD_PROPERTY.equals(names.item(0).getTextContent().trim())) { + NodeList values = property.getElementsByTagName("value"); + + if (values.getLength() > 0) { + String value = values.item(0).getTextContent().trim(); + LOG.info("RangerApplicationContextInitializer: read {}={} from {}", AUTH_METHOD_PROPERTY, value, CONFIG_RESOURCE); + return value; + } + } + } + } catch (Exception e) { + LOG.error("RangerApplicationContextInitializer: failed to read {} — defaulting to NONE", CONFIG_RESOURCE, e); + } + LOG.warn("RangerApplicationContextInitializer: {} not found in {}; defaulting to NONE", AUTH_METHOD_PROPERTY, CONFIG_RESOURCE); + return "NONE"; + } +} diff --git a/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java b/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java index db622f277f..160580ac6f 100644 --- a/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java +++ b/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java @@ -16,9 +16,10 @@ * specific language governing permissions and limitations * under the License. */ - package org.apache.ranger.security.web.saml; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.io.FileSystemResource; import org.springframework.security.converter.RsaKeyConverters; import org.springframework.security.saml2.core.Saml2X509Credential; @@ -31,9 +32,21 @@ import java.security.interfaces.RSAPrivateKey; public class RangerSamlRegistrationFactory { - private RangerSamlRegistrationFactory(){} + private static final Logger LOG = LoggerFactory.getLogger(RangerSamlRegistrationFactory.class); + + private RangerSamlRegistrationFactory() { + } public static RelyingPartyRegistration buildWithSigningCredential(RelyingPartyRegistration.Builder builder, String privateKeyPath, String certPath) throws Exception { + // If SAML is not configured, return a basic registration without signing credentials + if (privateKeyPath == null || privateKeyPath.trim().isEmpty() || certPath == null || certPath.trim().isEmpty()) { + LOG.info("SAML signing credentials not configured, skipping credential setup"); + return builder + .singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo") + .singleLogoutServiceResponseLocation("{baseUrl}/logout/saml2/slo") + .singleLogoutServiceBinding(Saml2MessageBinding.POST) + .build(); + } RSAPrivateKey privateKey = RsaKeyConverters.pkcs8().convert(new FileSystemResource(privateKeyPath).getInputStream()); X509Certificate certificate; try (FileInputStream fis = new FileInputStream(certPath)) { @@ -44,7 +57,7 @@ public static RelyingPartyRegistration buildWithSigningCredential(RelyingPartyRe .signingX509Credentials(c -> c.add(signingCredential)) .singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo") .singleLogoutServiceResponseLocation("{baseUrl}/logout/saml2/slo") - .singleLogoutServiceBinding(Saml2MessageBinding.REDIRECT) + .singleLogoutServiceBinding(Saml2MessageBinding.POST) .build(); } } 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 30f9944d00..6af7bbb598 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 @@ -109,7 +109,7 @@ ranger.authentication.method - SAML + NONE @@ -477,17 +477,17 @@ ranger.saml.idp.metadata.location - file:/opt/ranger/ranger-3.0.0-SNAPSHOT-admin/conf/saml/keycloak-IdP-metadata.xml + IdP metadata file location (file: or http: URI) ranger.saml.sp.key - /opt/ranger/ranger-3.0.0-SNAPSHOT-admin/conf/saml/ranger-sp-clean.key + Absolute path to the SP private key (PKCS8 PEM) used to sign AuthnRequests ranger.saml.sp.cert - /opt/ranger/ranger-3.0.0-SNAPSHOT-admin/conf/saml/ranger-sp.crt + Absolute path to the SP public certificate (X.509 PEM) 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 04b1fec59a..38473d6f88 100644 --- a/security-admin/src/main/resources/conf.dist/security-applicationContext.xml +++ b/security-admin/src/main/resources/conf.dist/security-applicationContext.xml @@ -52,39 +52,211 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -93,15 +265,11 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> - - - - + - + @@ -116,56 +284,9 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -180,7 +301,7 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> - + WHERE usr.LOGIN_ID=? AND usr_role.USER_ID = usr.ID" /> - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + diff --git a/security-admin/src/main/webapp/WEB-INF/web.xml b/security-admin/src/main/webapp/WEB-INF/web.xml index c2f1c985ed..1781ba7933 100644 --- a/security-admin/src/main/webapp/WEB-INF/web.xml +++ b/security-admin/src/main/webapp/WEB-INF/web.xml @@ -21,6 +21,10 @@ index.html + + contextInitializerClasses + org.apache.ranger.security.context.RangerApplicationContextInitializer + contextConfigLocation META-INF/applicationContext.xml diff --git a/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBarBody.jsx b/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBarBody.jsx index 351be2af2f..1812d9ea93 100644 --- a/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBarBody.jsx +++ b/security-admin/src/main/webapp/react-webapp/src/views/SideBar/SideBarBody.jsx @@ -220,6 +220,8 @@ export const SideBarBody = (props) => { if (checkKnoxSSOresp?.status == "419") { setUserProfile(null); window.location.replace("login.jsp"); + } else { + handleLogout(null); } console.error(`Error occurred while logout! ${error}`); } @@ -245,7 +247,7 @@ export const SideBarBody = (props) => { "cache-control": "no-cache" } }); - if (checkKnoxSSOVal !== undefined || checkKnoxSSOVal !== null) { + if (checkKnoxSSOVal !== undefined && checkKnoxSSOVal !== null) { if (checkKnoxSSOVal?.toString() == "false") { window.location.replace("locallogin"); window.localStorage.clear(); From 0f18cb752e3cc6c88c1ff6abd182325523116608 Mon Sep 17 00:00:00 2001 From: Vyom Mani Tiwari Date: Mon, 30 Mar 2026 13:18:04 +0530 Subject: [PATCH 4/6] did some xml reformating and cleanup --- .../resources/conf.dist/ranger-admin-site.xml | 2 +- .../conf.dist/security-applicationContext.xml | 597 +++++++++--------- 2 files changed, 307 insertions(+), 292 deletions(-) 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 6af7bbb598..269cb49ab8 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 @@ -473,7 +473,7 @@ ranger.saml.entity.id ranger-saml - SAML SP Entity ID — must match the Client ID registered in the IdP (e.g. Keycloak) + SAML SP Entity ID must match the Client ID registered in the IdP (e.g. Keycloak) ranger.saml.idp.metadata.location 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 38473d6f88..790c8ef7e2 100644 --- a/security-admin/src/main/resources/conf.dist/security-applicationContext.xml +++ b/security-admin/src/main/resources/conf.dist/security-applicationContext.xml @@ -16,12 +16,12 @@ limitations under the License. --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + /> + + + - + - - + + From 0f4f7bc2cc4cb6ad1979df3a8c1e4e33a9efc553 Mon Sep 17 00:00:00 2001 From: Vyom Mani Tiwari Date: Mon, 6 Apr 2026 12:55:59 +0530 Subject: [PATCH 5/6] if certificates are not set corrrectly throw exception --- .../security/web/saml/RangerSamlRegistrationFactory.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java b/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java index 160580ac6f..964b2d3895 100644 --- a/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java +++ b/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java @@ -38,14 +38,11 @@ private RangerSamlRegistrationFactory() { } public static RelyingPartyRegistration buildWithSigningCredential(RelyingPartyRegistration.Builder builder, String privateKeyPath, String certPath) throws Exception { - // If SAML is not configured, return a basic registration without signing credentials if (privateKeyPath == null || privateKeyPath.trim().isEmpty() || certPath == null || certPath.trim().isEmpty()) { LOG.info("SAML signing credentials not configured, skipping credential setup"); - return builder - .singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo") - .singleLogoutServiceResponseLocation("{baseUrl}/logout/saml2/slo") - .singleLogoutServiceBinding(Saml2MessageBinding.POST) - .build(); + throw new IllegalStateException("SAML is enabled but signing credentials are not configured. " + + "Set ranger.saml.sp.key and ranger.saml.sp.cert in ranger-admin-site.xml " + + "before starting Ranger with SAML authentication."); } RSAPrivateKey privateKey = RsaKeyConverters.pkcs8().convert(new FileSystemResource(privateKeyPath).getInputStream()); X509Certificate certificate; From d64fc4e916055700a5bd21b840fbfe5b8e29310a Mon Sep 17 00:00:00 2001 From: Vyom Mani Tiwari Date: Mon, 20 Apr 2026 13:35:21 +0530 Subject: [PATCH 6/6] simplify the ssl setup and fixed the logout issue --- .../saml/RangerSamlRegistrationFactory.java | 28 +++++++++++++++++++ .../resources/conf.dist/ranger-admin-site.xml | 10 ------- .../conf.dist/security-applicationContext.xml | 19 +++++++++---- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java b/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java index 964b2d3895..8a1f56a816 100644 --- a/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java +++ b/security-admin/src/main/java/org/apache/ranger/security/web/saml/RangerSamlRegistrationFactory.java @@ -27,6 +27,8 @@ import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; import java.io.FileInputStream; +import java.security.KeyStore; +import java.security.PrivateKey; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; @@ -57,4 +59,30 @@ public static RelyingPartyRegistration buildWithSigningCredential(RelyingPartyRe .singleLogoutServiceBinding(Saml2MessageBinding.POST) .build(); } + + public static RelyingPartyRegistration buildFromKeystore(RelyingPartyRegistration.Builder builder, String keystorePath, + String alias, String password) throws Exception { + if (keystorePath == null || keystorePath.trim().isEmpty()) { + throw new IllegalArgumentException("ranger.service.https.attrib.keystore.file must not be null or empty"); + } + if (alias == null || alias.trim().isEmpty()) { + throw new IllegalArgumentException("ranger.service.https.attrib.keystore.keyalias must not be null or empty"); + } + if (password == null || password.trim().isEmpty()) { + throw new IllegalArgumentException("ranger.service.https.attrib.keystore.pass must not be null or empty"); + } + KeyStore ks = KeyStore.getInstance("JKS"); + try (FileInputStream fis = new FileInputStream(keystorePath)) { + ks.load(fis, password.toCharArray()); + } + PrivateKey privateKey = (PrivateKey) ks.getKey(alias, password.toCharArray()); + X509Certificate cert = (X509Certificate) ks.getCertificate(alias); + Saml2X509Credential signingCredential = Saml2X509Credential.signing((RSAPrivateKey) privateKey, cert); + return builder + .signingX509Credentials(c -> c.add(signingCredential)) + .singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo") + .singleLogoutServiceResponseLocation("{baseUrl}/logout/saml2/slo") + .singleLogoutServiceBinding(Saml2MessageBinding.POST) + .build(); + } } 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 269cb49ab8..7b92c777da 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 @@ -480,16 +480,6 @@ IdP metadata file location (file: or http: URI) - - ranger.saml.sp.key - - Absolute path to the SP private key (PKCS8 PEM) used to sign AuthnRequests - - - ranger.saml.sp.cert - - Absolute path to the SP public certificate (X.509 PEM) - ranger.saml.success.url /index.html 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 790c8ef7e2..dd4beb1f4d 100644 --- a/security-admin/src/main/resources/conf.dist/security-applicationContext.xml +++ b/security-admin/src/main/resources/conf.dist/security-applicationContext.xml @@ -120,8 +120,16 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> - + + @@ -215,10 +223,11 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> + factory-method="buildFromKeystore"> - - + + +