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 @@
@@ -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 @@
@@ -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