Browse Source
Author: Manikumar Reddy <manikumar.reddy@gmail.com> Reviewers: Sriharsha Chintalapani <sriharsha@apache.org> Closes #5684 from omkreddy/KAFKA-5462-SSL-Namepull/5781/head
Manikumar Reddy
6 years ago
13 changed files with 437 additions and 20 deletions
@ -0,0 +1,197 @@ |
|||||||
|
/* |
||||||
|
* 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.kafka.common.security.ssl; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Locale; |
||||||
|
import java.util.regex.Matcher; |
||||||
|
import java.util.regex.Pattern; |
||||||
|
|
||||||
|
public class SslPrincipalMapper { |
||||||
|
|
||||||
|
private static final Pattern RULE_PARSER = Pattern.compile("((DEFAULT)|(RULE:(([^/]*)/([^/]*))/([LU])?))"); |
||||||
|
|
||||||
|
private final List<Rule> rules; |
||||||
|
|
||||||
|
public SslPrincipalMapper(List<Rule> sslPrincipalMappingRules) { |
||||||
|
this.rules = sslPrincipalMappingRules; |
||||||
|
} |
||||||
|
|
||||||
|
public static SslPrincipalMapper fromRules(List<String> sslPrincipalMappingRules) { |
||||||
|
List<String> rules = sslPrincipalMappingRules == null ? Collections.singletonList("DEFAULT") : sslPrincipalMappingRules; |
||||||
|
return new SslPrincipalMapper(parseRules(rules)); |
||||||
|
} |
||||||
|
|
||||||
|
private static List<Rule> parseRules(List<String> rules) { |
||||||
|
List<Rule> result = new ArrayList<>(); |
||||||
|
for (String rule : rules) { |
||||||
|
Matcher matcher = RULE_PARSER.matcher(rule); |
||||||
|
if (!matcher.lookingAt()) { |
||||||
|
throw new IllegalArgumentException("Invalid rule: " + rule); |
||||||
|
} |
||||||
|
if (rule.length() != matcher.end()) { |
||||||
|
throw new IllegalArgumentException("Invalid rule: `" + rule + "`, unmatched substring: `" + rule.substring(matcher.end()) + "`"); |
||||||
|
} |
||||||
|
if (matcher.group(2) != null) { |
||||||
|
result.add(new Rule()); |
||||||
|
} else { |
||||||
|
result.add(new Rule(matcher.group(5), |
||||||
|
matcher.group(6), |
||||||
|
"L".equals(matcher.group(7)), |
||||||
|
"U".equals(matcher.group(7)))); |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
public String getName(String distinguishedName) throws IOException { |
||||||
|
for (Rule r : rules) { |
||||||
|
String principalName = r.apply(distinguishedName); |
||||||
|
if (principalName != null) { |
||||||
|
return principalName; |
||||||
|
} |
||||||
|
} |
||||||
|
throw new NoMatchingRule("No rules apply to " + distinguishedName + ", rules " + rules); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return "SslPrincipalMapper(rules = " + rules + ")"; |
||||||
|
} |
||||||
|
|
||||||
|
public static class NoMatchingRule extends IOException { |
||||||
|
NoMatchingRule(String msg) { |
||||||
|
super(msg); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static class Rule { |
||||||
|
private static final Pattern BACK_REFERENCE_PATTERN = Pattern.compile("\\$(\\d+)"); |
||||||
|
|
||||||
|
private final boolean isDefault; |
||||||
|
private final Pattern pattern; |
||||||
|
private final String replacement; |
||||||
|
private final boolean toLowerCase; |
||||||
|
private final boolean toUpperCase; |
||||||
|
|
||||||
|
Rule() { |
||||||
|
isDefault = true; |
||||||
|
pattern = null; |
||||||
|
replacement = null; |
||||||
|
toLowerCase = false; |
||||||
|
toUpperCase = false; |
||||||
|
} |
||||||
|
|
||||||
|
Rule(String pattern, String replacement, boolean toLowerCase, boolean toUpperCase) { |
||||||
|
isDefault = false; |
||||||
|
this.pattern = pattern == null ? null : Pattern.compile(pattern); |
||||||
|
this.replacement = replacement; |
||||||
|
this.toLowerCase = toLowerCase; |
||||||
|
this.toUpperCase = toUpperCase; |
||||||
|
} |
||||||
|
|
||||||
|
String apply(String distinguishedName) { |
||||||
|
if (isDefault) { |
||||||
|
return distinguishedName; |
||||||
|
} |
||||||
|
|
||||||
|
String result = null; |
||||||
|
final Matcher m = pattern.matcher(distinguishedName); |
||||||
|
|
||||||
|
if (m.matches()) { |
||||||
|
result = distinguishedName.replaceAll(pattern.pattern(), escapeLiteralBackReferences(replacement, m.groupCount())); |
||||||
|
} |
||||||
|
|
||||||
|
if (toLowerCase && result != null) { |
||||||
|
result = result.toLowerCase(Locale.ENGLISH); |
||||||
|
} else if (toUpperCase & result != null) { |
||||||
|
result = result.toUpperCase(Locale.ENGLISH); |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
//If we find a back reference that is not valid, then we will treat it as a literal string. For example, if we have 3 capturing
|
||||||
|
//groups and the Replacement Value has the value is "$1@$4", then we want to treat the $4 as a literal "$4", rather
|
||||||
|
//than attempting to use it as a back reference.
|
||||||
|
//This method was taken from Apache Nifi project : org.apache.nifi.authorization.util.IdentityMappingUtil
|
||||||
|
private String escapeLiteralBackReferences(final String unescaped, final int numCapturingGroups) { |
||||||
|
if (numCapturingGroups == 0) { |
||||||
|
return unescaped; |
||||||
|
} |
||||||
|
|
||||||
|
String value = unescaped; |
||||||
|
final Matcher backRefMatcher = BACK_REFERENCE_PATTERN.matcher(value); |
||||||
|
while (backRefMatcher.find()) { |
||||||
|
final String backRefNum = backRefMatcher.group(1); |
||||||
|
if (backRefNum.startsWith("0")) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
final int originalBackRefIndex = Integer.parseInt(backRefNum); |
||||||
|
int backRefIndex = originalBackRefIndex; |
||||||
|
|
||||||
|
|
||||||
|
// if we have a replacement value like $123, and we have less than 123 capturing groups, then
|
||||||
|
// we want to truncate the 3 and use capturing group 12; if we have less than 12 capturing groups,
|
||||||
|
// then we want to truncate the 2 and use capturing group 1; if we don't have a capturing group then
|
||||||
|
// we want to truncate the 1 and get 0.
|
||||||
|
while (backRefIndex > numCapturingGroups && backRefIndex >= 10) { |
||||||
|
backRefIndex /= 10; |
||||||
|
} |
||||||
|
|
||||||
|
if (backRefIndex > numCapturingGroups) { |
||||||
|
final StringBuilder sb = new StringBuilder(value.length() + 1); |
||||||
|
final int groupStart = backRefMatcher.start(1); |
||||||
|
|
||||||
|
sb.append(value.substring(0, groupStart - 1)); |
||||||
|
sb.append("\\"); |
||||||
|
sb.append(value.substring(groupStart - 1)); |
||||||
|
value = sb.toString(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
StringBuilder buf = new StringBuilder(); |
||||||
|
if (isDefault) { |
||||||
|
buf.append("DEFAULT"); |
||||||
|
} else { |
||||||
|
buf.append("RULE:"); |
||||||
|
if (pattern != null) { |
||||||
|
buf.append(pattern); |
||||||
|
} |
||||||
|
if (replacement != null) { |
||||||
|
buf.append("/"); |
||||||
|
buf.append(replacement); |
||||||
|
} |
||||||
|
if (toLowerCase) { |
||||||
|
buf.append("/L"); |
||||||
|
} else if (toUpperCase) { |
||||||
|
buf.append("/U"); |
||||||
|
} |
||||||
|
} |
||||||
|
return buf.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,85 @@ |
|||||||
|
/* |
||||||
|
* 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.kafka.common.security.ssl; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals; |
||||||
|
import static org.junit.Assert.fail; |
||||||
|
|
||||||
|
public class SslPrincipalMapperTest { |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testValidRules() { |
||||||
|
testValidRule(Arrays.asList("DEFAULT")); |
||||||
|
testValidRule(Arrays.asList("RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/")); |
||||||
|
testValidRule(Arrays.asList("RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/L", "DEFAULT")); |
||||||
|
testValidRule(Arrays.asList("RULE:^CN=(.*?),OU=(.*?),O=(.*?),L=(.*?),ST=(.*?),C=(.*?)$/$1@$2/")); |
||||||
|
testValidRule(Arrays.asList("RULE:^.*[Cc][Nn]=([a-zA-Z0-9.]*).*$/$1/L")); |
||||||
|
testValidRule(Arrays.asList("RULE:^cn=(.?),ou=(.?),dc=(.?),dc=(.?)$/$1@$2/U")); |
||||||
|
} |
||||||
|
|
||||||
|
private void testValidRule(List<String> rules) { |
||||||
|
SslPrincipalMapper.fromRules(rules); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testInvalidRules() { |
||||||
|
testInvalidRule(Arrays.asList("default")); |
||||||
|
testInvalidRule(Arrays.asList("DEFAUL")); |
||||||
|
testInvalidRule(Arrays.asList("DEFAULT/L")); |
||||||
|
testInvalidRule(Arrays.asList("DEFAULT/U")); |
||||||
|
|
||||||
|
testInvalidRule(Arrays.asList("RULE:CN=(.*?),OU=ServiceUsers.*/$1")); |
||||||
|
testInvalidRule(Arrays.asList("rule:^CN=(.*?),OU=ServiceUsers.*$/$1/")); |
||||||
|
testInvalidRule(Arrays.asList("RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/L/U")); |
||||||
|
testInvalidRule(Arrays.asList("RULE:^CN=(.*?),OU=ServiceUsers.*$/L")); |
||||||
|
testInvalidRule(Arrays.asList("RULE:^CN=(.*?),OU=ServiceUsers.*$/U")); |
||||||
|
testInvalidRule(Arrays.asList("RULE:^CN=(.*?),OU=ServiceUsers.*$/LU")); |
||||||
|
} |
||||||
|
|
||||||
|
private void testInvalidRule(List<String> rules) { |
||||||
|
try { |
||||||
|
System.out.println(SslPrincipalMapper.fromRules(rules)); |
||||||
|
fail("should have thrown IllegalArgumentException"); |
||||||
|
} catch (IllegalArgumentException e) { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testSslPrincipalMapper() throws Exception { |
||||||
|
List<String> rules = Arrays.asList( |
||||||
|
"RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/L", |
||||||
|
"RULE:^CN=(.*?),OU=(.*?),O=(.*?),L=(.*?),ST=(.*?),C=(.*?)$/$1@$2/L", |
||||||
|
"RULE:^cn=(.*?),ou=(.*?),dc=(.*?),dc=(.*?)$/$1@$2/U", |
||||||
|
"RULE:^.*[Cc][Nn]=([a-zA-Z0-9.]*).*$/$1/U", |
||||||
|
"DEFAULT" |
||||||
|
); |
||||||
|
|
||||||
|
SslPrincipalMapper mapper = SslPrincipalMapper.fromRules(rules); |
||||||
|
|
||||||
|
assertEquals("duke", mapper.getName("CN=Duke,OU=ServiceUsers,O=Org,C=US")); |
||||||
|
assertEquals("duke@sme", mapper.getName("CN=Duke,OU=SME,O=mycp,L=Fulton,ST=MD,C=US")); |
||||||
|
assertEquals("DUKE@SME", mapper.getName("cn=duke,ou=sme,dc=mycp,dc=com")); |
||||||
|
assertEquals("DUKE", mapper.getName("cN=duke,OU=JavaSoft,O=Sun Microsystems")); |
||||||
|
assertEquals("OU=JavaSoft,O=Sun Microsystems,C=US", mapper.getName("OU=JavaSoft,O=Sun Microsystems,C=US")); |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue