Repository: incubator-ranger Updated Branches: refs/heads/ranger-0.5 bc776c0cd -> ffe0013b1
RANGER-746: Addressing suggestions from Review - Add wildcard, multiple CN & SAN support when validating plugins' SSL certs Signed-off-by: sneethiraj <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/incubator-ranger/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ranger/commit/ffe0013b Tree: http://git-wip-us.apache.org/repos/asf/incubator-ranger/tree/ffe0013b Diff: http://git-wip-us.apache.org/repos/asf/incubator-ranger/diff/ffe0013b Branch: refs/heads/ranger-0.5 Commit: ffe0013b1ce4f610d71aeeedec64905846c67e4a Parents: bc776c0 Author: Velmurugan Periasamy <[email protected]> Authored: Wed Mar 23 18:42:11 2016 -0400 Committer: sneethiraj <[email protected]> Committed: Wed Mar 23 22:44:59 2016 -0400 ---------------------------------------------------------------------- .../org/apache/ranger/common/ServiceUtil.java | 169 ++++++++++++++----- 1 file changed, 130 insertions(+), 39 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/ffe0013b/security-admin/src/main/java/org/apache/ranger/common/ServiceUtil.java ---------------------------------------------------------------------- diff --git a/security-admin/src/main/java/org/apache/ranger/common/ServiceUtil.java b/security-admin/src/main/java/org/apache/ranger/common/ServiceUtil.java index 861240b..a3922de 100644 --- a/security-admin/src/main/java/org/apache/ranger/common/ServiceUtil.java +++ b/security-admin/src/main/java/org/apache/ranger/common/ServiceUtil.java @@ -21,6 +21,7 @@ package org.apache.ranger.common; import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -65,6 +66,8 @@ import org.springframework.stereotype.Component; @Component public class ServiceUtil { static final Logger LOG = Logger.getLogger(ServiceUtil.class); + private static final String REGEX_PREFIX_STR = "regex:" ; + private static final int REGEX_PREFIX_STR_LENGTH = REGEX_PREFIX_STR.length() ; static Map<String, Integer> mapServiceTypeToAssetType = new HashMap<String, Integer>(); static Map<String, Integer> mapAccessTypeToPermType = new HashMap<String, Integer>(); @@ -1313,10 +1316,8 @@ public class ServiceUtil { } - public boolean isValidateHttpsAuthentication( String serviceName, HttpServletRequest request) { - + public boolean isValidateHttpsAuthentication( String serviceName, HttpServletRequest request) { boolean isValidAuthentication=false; -// boolean httpEnabled = PropertiesUtil.getBooleanProperty("http.enabled",true); boolean httpEnabled = PropertiesUtil.getBooleanProperty("ranger.service.http.enabled",true); X509Certificate[] certchain = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); String ipAddress = request.getHeader("X-FORWARDED-FOR"); @@ -1324,13 +1325,13 @@ public class ServiceUtil { ipAddress = request.getRemoteAddr(); } boolean isSecure = request.isSecure(); - + if (serviceName == null || serviceName.isEmpty()) { LOG.error("ServiceName not provided"); throw restErrorUtil.createRESTException("Unauthorized access.", MessageEnums.OPER_NOT_ALLOWED_FOR_ENTITY); } - + RangerService service = null; try { service = svcStore.getServiceByName(serviceName); @@ -1362,50 +1363,140 @@ public class ServiceUtil { + " unable to get client certificate", MessageEnums.OPER_NOT_ALLOWED_FOR_ENTITY); } - } - String commonName = null; - if (certchain != null) { - X509Certificate clientCert = certchain[0]; - String dn = clientCert.getSubjectX500Principal().getName(); + + // Check if common name is found in service config + Map<String, String> configMap = service.getConfigs(); + String cnFromConfig = configMap.get("commonNameForCertificate"); + if (cnFromConfig == null || "".equals(cnFromConfig.trim())) { + LOG.error("Unauthorized access. No common name for certificate set. Please check your service config"); + throw restErrorUtil.createRESTException("Unauthorized access. No common name for certificate set. Please check your service config", + MessageEnums.OPER_NOT_ALLOWED_FOR_ENTITY); + } + + + String cnFromConfigForTest = cnFromConfig ; + boolean isRegEx = cnFromConfig.toLowerCase().startsWith(REGEX_PREFIX_STR) ; + if (isRegEx) { + cnFromConfigForTest = cnFromConfig.substring(REGEX_PREFIX_STR_LENGTH) ; + } + + // Perform SAN validation try { - LdapName ln = new LdapName(dn); - for (Rdn rdn : ln.getRdns()) { - if (rdn.getType().equalsIgnoreCase("CN")) { - commonName = rdn.getValue() + ""; - break; + Collection<List<?>> subjectAltNames = certchain[0].getSubjectAlternativeNames(); + if (subjectAltNames != null) { + for (List<?> sanItem : subjectAltNames) { + if (sanItem.size() == 2) { + Integer sanType = (Integer) sanItem.get(0); + String sanValue = (String) sanItem.get(1); + if ( (sanType == 2 || sanType == 7) && (matchNames(sanValue, cnFromConfigForTest,isRegEx)) ) { + if (LOG.isDebugEnabled()) LOG.debug("Client Cert verification successful, matched SAN:" + sanValue); + isValidAuthentication=true; + break; + } + } } } - if (commonName == null) { - LOG.error("Unauthorized access. CName is null. serviceName=" + serviceName); - throw restErrorUtil.createRESTException( - "Unauthorized access - Unable to find Common Name from [" - + dn + "]", - MessageEnums.OPER_NOT_ALLOWED_FOR_ENTITY); - } - } catch (InvalidNameException e) { - LOG.error("Invalid Common Name. CName=" + commonName + ", serviceName=" + serviceName, e); - throw restErrorUtil.createRESTException( - "Unauthorized access - Invalid Common Name", - MessageEnums.OPER_NOT_ALLOWED_FOR_ENTITY); + } catch (Throwable e) { + LOG.error("Unauthorized access. Error getting SAN from certificate", e); + throw restErrorUtil.createRESTException("Unauthorized access - Error getting SAN from client certificate", MessageEnums.OPER_NOT_ALLOWED_FOR_ENTITY); } - } - if (commonName != null) { - Map<String, String> configMap = service.getConfigs(); - String cnFromConfig = configMap.get("commonNameForCertificate"); - if (cnFromConfig == null - || !commonName.equalsIgnoreCase(cnFromConfig)) { - LOG.error("Unauthorized access. expected [" + cnFromConfig + "], found [" - + commonName + "], serviceName=" + serviceName); - throw restErrorUtil.createRESTException( - "Unauthorized access. expected [" + cnFromConfig + // Perform common name validation only if SAN validation did not succeed + if (!isValidAuthentication) { + String commonName = null; + if (certchain != null) { + X509Certificate clientCert = certchain[0]; + String dn = clientCert.getSubjectX500Principal().getName(); + try { + LdapName ln = new LdapName(dn); + for (Rdn rdn : ln.getRdns()) { + if (rdn.getType().equalsIgnoreCase("CN")) { + commonName = rdn.getValue() + ""; + break; + } + } + if (commonName == null) { + LOG.error("Unauthorized access. CName is null. serviceName=" + serviceName); + throw restErrorUtil.createRESTException( + "Unauthorized access - Unable to find Common Name from [" + + dn + "]", + MessageEnums.OPER_NOT_ALLOWED_FOR_ENTITY); + } + } catch (InvalidNameException e) { + LOG.error("Invalid Common Name. CName=" + commonName + ", serviceName=" + serviceName, e); + throw restErrorUtil.createRESTException( + "Unauthorized access - Invalid Common Name", + MessageEnums.OPER_NOT_ALLOWED_FOR_ENTITY); + } + } + if (commonName != null) { + if (matchNames(commonName, cnFromConfigForTest,isRegEx)) { + if (LOG.isDebugEnabled()) LOG.debug("Client Cert verification successful, matched CN " + commonName + " with " + cnFromConfigForTest + ", wildcard match = " + isRegEx); + isValidAuthentication=true; + } + + if (!isValidAuthentication) { + LOG.error("Unauthorized access. expected [" + cnFromConfigForTest + "], found [" + + commonName + "], serviceName=" + serviceName); + throw restErrorUtil.createRESTException( + "Unauthorized access. expected [" + cnFromConfigForTest + "], found [" + commonName + "]", - MessageEnums.OPER_NOT_ALLOWED_FOR_ENTITY); + MessageEnums.OPER_NOT_ALLOWED_FOR_ENTITY); + } + } } + } else { + isValidAuthentication = true; } - isValidAuthentication=true; return isValidAuthentication; } + + private boolean matchNames(String target, String source, boolean wildcardMatch) { + boolean matched = false; + if(target != null && source != null) { + String names[] = (wildcardMatch ? new String[] { source } : source.split(",")); + for (String n:names) { + + if (wildcardMatch) { + if(LOG.isDebugEnabled()) LOG.debug("Wildcard Matching [" + target + "] with [" + n + "]"); + if (wildcardMatch(target,n)) { + if(LOG.isDebugEnabled()) LOG.debug("Matched target:" + target + " with " + n); + matched = true; + break; + } + } else { + if(LOG.isDebugEnabled()) LOG.debug("Matching [" + target + "] with [" + n + "]"); + if (target.equalsIgnoreCase(n)) { + if(LOG.isDebugEnabled()) LOG.debug("Matched target:" + target + " with " + n); + matched = true; + break; + } + } + } + } else { + if(LOG.isDebugEnabled()) LOG.debug("source=[" + source + "],target=[" + target +"], returning false."); + } + return matched; + } + + private boolean matchNames(String target, String source) { + return matchNames(target,source,false); + } + + private boolean wildcardMatch(String target, String source) { + boolean matched = false; + if(target != null && source != null) { + try { + matched = target.matches(source); + } catch (Throwable e) { + LOG.error("Error doing wildcard match..", e); + } + } else { + if(LOG.isDebugEnabled()) LOG.debug("source=[" + source + "],target=[" + target +"], returning false."); + } + return matched; + } + private Boolean toBooleanReplacePerm(boolean isReplacePermission) {
