KAFKA-8860: Let SslPrincipalMapper split SSL principal mapping rules

Author: teebee <tb@teebee.de>

Reviewers: Reviewers: Manikumar Reddy <manikumar.reddy@gmail.com>

Closes #7140 from teebee/teebee/ssl-principal-mapping-rules-handling
This commit is contained in:
teebee 2019-09-02 23:32:49 +05:30 committed by Manikumar Reddy
parent 364794866f
commit 88d1b6de1f
6 changed files with 97 additions and 89 deletions

View File

@ -55,7 +55,7 @@ public class BrokerSecurityConfigs {
" see <a href=\"#security_authz\"> security authorization and acls</a>. Note that this configuration is ignored" + " see <a href=\"#security_authz\"> security authorization and acls</a>. Note that this configuration is ignored" +
" if an extension of KafkaPrincipalBuilder is provided by the <code>" + PRINCIPAL_BUILDER_CLASS_CONFIG + "</code>" + " if an extension of KafkaPrincipalBuilder is provided by the <code>" + PRINCIPAL_BUILDER_CLASS_CONFIG + "</code>" +
" configuration."; " configuration.";
public static final List<String> DEFAULT_SSL_PRINCIPAL_MAPPING_RULES = Collections.singletonList("DEFAULT"); public static final String DEFAULT_SSL_PRINCIPAL_MAPPING_RULES = "DEFAULT";
public static final String SASL_KERBEROS_PRINCIPAL_TO_LOCAL_RULES_DOC = "A list of rules for mapping from principal " + public static final String SASL_KERBEROS_PRINCIPAL_TO_LOCAL_RULES_DOC = "A list of rules for mapping from principal " +
"names to short names (typically operating system usernames). The rules are evaluated in order and the " + "names to short names (typically operating system usernames). The rules are evaluated in order and the " +

View File

@ -35,7 +35,6 @@ import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey; import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel; import java.nio.channels.SocketChannel;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -63,8 +62,7 @@ public class SslChannelBuilder implements ChannelBuilder, ListenerReconfigurable
public void configure(Map<String, ?> configs) throws KafkaException { public void configure(Map<String, ?> configs) throws KafkaException {
try { try {
this.configs = configs; this.configs = configs;
@SuppressWarnings("unchecked") String sslPrincipalMappingRules = (String) configs.get(BrokerSecurityConfigs.SSL_PRINCIPAL_MAPPING_RULES_CONFIG);
List<String> sslPrincipalMappingRules = (List<String>) configs.get(BrokerSecurityConfigs.SSL_PRINCIPAL_MAPPING_RULES_CONFIG);
if (sslPrincipalMappingRules != null) if (sslPrincipalMappingRules != null)
sslPrincipalMapper = SslPrincipalMapper.fromRules(sslPrincipalMappingRules); sslPrincipalMapper = SslPrincipalMapper.fromRules(sslPrincipalMappingRules);
this.sslFactory = new SslFactory(mode, null, isInterBrokerListener); this.sslFactory = new SslFactory(mode, null, isInterBrokerListener);

View File

@ -18,53 +18,44 @@ package org.apache.kafka.common.security.ssl;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Collections;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static org.apache.kafka.common.config.internals.BrokerSecurityConfigs.DEFAULT_SSL_PRINCIPAL_MAPPING_RULES;
public class SslPrincipalMapper { public class SslPrincipalMapper {
private static final Pattern RULE_PARSER = Pattern.compile("((DEFAULT)|(RULE:(([^/]*)/([^/]*))/([LU])?))"); private static final String RULE_PATTERN = "(DEFAULT)|RULE:((\\\\.|[^\\\\/])*)/((\\\\.|[^\\\\/])*)/([LU]?).*?|(.*?)";
private static final Pattern RULE_SPLITTER = Pattern.compile("\\s*(" + RULE_PATTERN + ")\\s*(,\\s*|$)");
private static final Pattern RULE_PARSER = Pattern.compile(RULE_PATTERN);
private final List<Rule> rules; private final List<Rule> rules;
public SslPrincipalMapper(List<Rule> sslPrincipalMappingRules) { public SslPrincipalMapper(String sslPrincipalMappingRules) {
this.rules = sslPrincipalMappingRules; this.rules = parseRules(splitRules(sslPrincipalMappingRules));
} }
public static SslPrincipalMapper fromRules(List<String> sslPrincipalMappingRules) { public static SslPrincipalMapper fromRules(String sslPrincipalMappingRules) {
List<String> rules = sslPrincipalMappingRules == null ? Collections.singletonList("DEFAULT") : sslPrincipalMappingRules; return new SslPrincipalMapper(sslPrincipalMappingRules);
return new SslPrincipalMapper(parseRules(rules));
} }
private static List<String> joinSplitRules(List<String> rules) { private static List<String> splitRules(String sslPrincipalMappingRules) {
String rule = "RULE:"; if (sslPrincipalMappingRules == null) {
String defaultRule = "DEFAULT"; sslPrincipalMappingRules = DEFAULT_SSL_PRINCIPAL_MAPPING_RULES;
List<String> retVal = new ArrayList<>();
StringBuilder currentRule = new StringBuilder();
for (String r : rules) {
if (currentRule.length() > 0) {
if (r.startsWith(rule) || r.equals(defaultRule)) {
retVal.add(currentRule.toString());
currentRule.setLength(0);
currentRule.append(r);
} else {
currentRule.append(String.format(",%s", r));
}
} else {
currentRule.append(r);
}
} }
if (currentRule.length() > 0) {
retVal.add(currentRule.toString()); List<String> result = new ArrayList<>();
Matcher matcher = RULE_SPLITTER.matcher(sslPrincipalMappingRules.trim());
while (matcher.find()) {
result.add(matcher.group(1));
} }
return retVal;
return result;
} }
private static List<Rule> parseRules(List<String> rules) { private static List<Rule> parseRules(List<String> rules) {
rules = joinSplitRules(rules);
List<Rule> result = new ArrayList<>(); List<Rule> result = new ArrayList<>();
for (String rule : rules) { for (String rule : rules) {
Matcher matcher = RULE_PARSER.matcher(rule); Matcher matcher = RULE_PARSER.matcher(rule);
@ -74,15 +65,18 @@ public class SslPrincipalMapper {
if (rule.length() != matcher.end()) { if (rule.length() != matcher.end()) {
throw new IllegalArgumentException("Invalid rule: `" + rule + "`, unmatched substring: `" + rule.substring(matcher.end()) + "`"); throw new IllegalArgumentException("Invalid rule: `" + rule + "`, unmatched substring: `" + rule.substring(matcher.end()) + "`");
} }
if (matcher.group(2) != null) {
// empty rules are ignored
if (matcher.group(1) != null) {
result.add(new Rule()); result.add(new Rule());
} else { } else if (matcher.group(2) != null) {
result.add(new Rule(matcher.group(5), result.add(new Rule(matcher.group(2),
matcher.group(6), matcher.group(4),
"L".equals(matcher.group(7)), "L".equals(matcher.group(6)),
"U".equals(matcher.group(7)))); "U".equals(matcher.group(6))));
} }
} }
return result; return result;
} }

View File

@ -30,8 +30,6 @@ import javax.net.ssl.SSLSession;
import javax.security.sasl.SaslServer; import javax.security.sasl.SaslServer;
import java.net.InetAddress; import java.net.InetAddress;
import java.security.Principal; import java.security.Principal;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
@ -143,7 +141,7 @@ public class DefaultKafkaPrincipalBuilderTest {
.thenReturn(new X500Principal("CN=duke, OU=JavaSoft, O=Sun Microsystems")) .thenReturn(new X500Principal("CN=duke, OU=JavaSoft, O=Sun Microsystems"))
.thenReturn(new X500Principal("OU=JavaSoft, O=Sun Microsystems, C=US")); .thenReturn(new X500Principal("OU=JavaSoft, O=Sun Microsystems, C=US"));
List<String> rules = Arrays.asList( String rules = String.join(", ",
"RULE:^CN=(.*),OU=ServiceUsers.*$/$1/L", "RULE:^CN=(.*),OU=ServiceUsers.*$/$1/L",
"RULE:^CN=(.*),OU=(.*),O=(.*),L=(.*),ST=(.*),C=(.*)$/$1@$2/L", "RULE:^CN=(.*),OU=(.*),O=(.*),L=(.*),ST=(.*),C=(.*)$/$1@$2/L",
"RULE:^.*[Cc][Nn]=([a-zA-Z0-9.]*).*$/$1/U", "RULE:^.*[Cc][Nn]=([a-zA-Z0-9.]*).*$/$1/U",

View File

@ -18,9 +18,6 @@ package org.apache.kafka.common.security.ssl;
import org.junit.Test; import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
@ -28,59 +25,37 @@ public class SslPrincipalMapperTest {
@Test @Test
public void testValidRules() { public void testValidRules() {
testValidRule(Arrays.asList("DEFAULT")); testValidRule("DEFAULT");
testValidRule(Arrays.asList("RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/")); testValidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/");
testValidRule(Arrays.asList("RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/L", "DEFAULT")); testValidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/L, DEFAULT");
testValidRule(Arrays.asList("RULE:^CN=(.*?),OU=(.*?),O=(.*?),L=(.*?),ST=(.*?),C=(.*?)$/$1@$2/")); testValidRule("RULE:^CN=(.*?),OU=(.*?),O=(.*?),L=(.*?),ST=(.*?),C=(.*?)$/$1@$2/");
testValidRule(Arrays.asList("RULE:^.*[Cc][Nn]=([a-zA-Z0-9.]*).*$/$1/L")); testValidRule("RULE:^.*[Cc][Nn]=([a-zA-Z0-9.]*).*$/$1/L");
testValidRule(Arrays.asList("RULE:^cn=(.?),ou=(.?),dc=(.?),dc=(.?)$/$1@$2/U")); testValidRule("RULE:^cn=(.?),ou=(.?),dc=(.?),dc=(.?)$/$1@$2/U");
testValidRule("RULE:^CN=([^,ADEFLTU,]+)(,.*|$)/$1/");
testValidRule("RULE:^CN=([^,DEFAULT,]+)(,.*|$)/$1/");
} }
@Test private void testValidRule(String rules) {
public void testValidSplitRules() {
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); SslPrincipalMapper.fromRules(rules);
} }
@Test @Test
public void testInvalidRules() { public void testInvalidRules() {
testInvalidRule(Arrays.asList("default")); testInvalidRule("default");
testInvalidRule(Arrays.asList("DEFAUL")); testInvalidRule("DEFAUL");
testInvalidRule(Arrays.asList("DEFAULT/L")); testInvalidRule("DEFAULT/L");
testInvalidRule(Arrays.asList("DEFAULT/U")); testInvalidRule("DEFAULT/U");
testInvalidRule(Arrays.asList("RULE:CN=(.*?),OU=ServiceUsers.*/$1")); testInvalidRule("RULE:CN=(.*?),OU=ServiceUsers.*/$1");
testInvalidRule(Arrays.asList("rule:^CN=(.*?),OU=ServiceUsers.*$/$1/")); testInvalidRule("rule:^CN=(.*?),OU=ServiceUsers.*$/$1/");
testInvalidRule(Arrays.asList("RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/L/U")); testInvalidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/L/U");
testInvalidRule(Arrays.asList("RULE:^CN=(.*?),OU=ServiceUsers.*$/L")); testInvalidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/L");
testInvalidRule(Arrays.asList("RULE:^CN=(.*?),OU=ServiceUsers.*$/U")); testInvalidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/U");
testInvalidRule(Arrays.asList("RULE:^CN=(.*?),OU=ServiceUsers.*$/LU")); testInvalidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/LU");
} }
@Test private void testInvalidRule(String rules) {
public void testInvalidSplitRules() {
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 { try {
System.out.println(SslPrincipalMapper.fromRules(rules)); System.out.println(SslPrincipalMapper.fromRules(rules));
fail("should have thrown IllegalArgumentException"); fail("should have thrown IllegalArgumentException");
@ -90,7 +65,7 @@ public class SslPrincipalMapperTest {
@Test @Test
public void testSslPrincipalMapper() throws Exception { public void testSslPrincipalMapper() throws Exception {
List<String> rules = Arrays.asList( String rules = String.join(", ",
"RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/L", "RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/L",
"RULE:^CN=(.*?),OU=(.*?),O=(.*?),L=(.*?),ST=(.*?),C=(.*?)$/$1@$2/L", "RULE:^CN=(.*?),OU=(.*?),O=(.*?),L=(.*?),ST=(.*?),C=(.*?)$/$1@$2/L",
"RULE:^cn=(.*?),ou=(.*?),dc=(.*?),dc=(.*?)$/$1@$2/U", "RULE:^cn=(.*?),ou=(.*?),dc=(.*?),dc=(.*?)$/$1@$2/U",
@ -107,4 +82,47 @@ public class SslPrincipalMapperTest {
assertEquals("OU=JavaSoft,O=Sun Microsystems,C=US", mapper.getName("OU=JavaSoft,O=Sun Microsystems,C=US")); assertEquals("OU=JavaSoft,O=Sun Microsystems,C=US", mapper.getName("OU=JavaSoft,O=Sun Microsystems,C=US"));
} }
private void testRulesSplitting(String expected, String rules) {
SslPrincipalMapper mapper = SslPrincipalMapper.fromRules(rules);
assertEquals(String.format("SslPrincipalMapper(rules = %s)", expected), mapper.toString());
}
@Test
public void testRulesSplitting() {
// seeing is believing
testRulesSplitting("[]", "");
testRulesSplitting("[DEFAULT]", "DEFAULT");
testRulesSplitting("[RULE:/]", "RULE://");
testRulesSplitting("[RULE:/.*]", "RULE:/.*/");
testRulesSplitting("[RULE:/.*/L]", "RULE:/.*/L");
testRulesSplitting("[RULE:/, DEFAULT]", "RULE://,DEFAULT");
testRulesSplitting("[RULE:/, DEFAULT]", " RULE:// , DEFAULT ");
testRulesSplitting("[RULE: / , DEFAULT]", " RULE: / / , DEFAULT ");
testRulesSplitting("[RULE: / /U, DEFAULT]", " RULE: / /U ,DEFAULT ");
testRulesSplitting("[RULE:([A-Z]*)/$1/U, RULE:([a-z]+)/$1, DEFAULT]", " RULE:([A-Z]*)/$1/U ,RULE:([a-z]+)/$1/, DEFAULT ");
// empty rules are ignored
testRulesSplitting("[]", ", , , , , , , ");
testRulesSplitting("[RULE:/, DEFAULT]", ",,RULE://,,,DEFAULT,,");
testRulesSplitting("[RULE: / , DEFAULT]", ", , RULE: / / ,,, DEFAULT, , ");
testRulesSplitting("[RULE: / /U, DEFAULT]", " , , RULE: / /U ,, ,DEFAULT, ,");
// escape sequences
testRulesSplitting("[RULE:\\/\\\\\\(\\)\\n\\t/\\/\\/]", "RULE:\\/\\\\\\(\\)\\n\\t/\\/\\//");
testRulesSplitting("[RULE:\\**\\/+/*/L, RULE:\\/*\\**/**]", "RULE:\\**\\/+/*/L,RULE:\\/*\\**/**/");
// rules rule
testRulesSplitting(
"[RULE:,RULE:,/,RULE:,\\//U, RULE:,/RULE:,, RULE:,RULE:,/L,RULE:,/L, RULE:, DEFAULT, /DEFAULT, DEFAULT]",
"RULE:,RULE:,/,RULE:,\\//U,RULE:,/RULE:,/,RULE:,RULE:,/L,RULE:,/L,RULE:, DEFAULT, /DEFAULT/,DEFAULT"
);
}
@Test
public void testCommaWithWhitespace() throws Exception {
String rules = "RULE:^CN=((\\\\, *|\\w)+)(,.*|$)/$1/,DEFAULT";
SslPrincipalMapper mapper = SslPrincipalMapper.fromRules(rules);
assertEquals("Tkac\\, Adam", mapper.getName("CN=Tkac\\, Adam,OU=ITZ,DC=geodis,DC=cz"));
}
} }

View File

@ -1059,7 +1059,7 @@ object KafkaConfig {
.define(SslSecureRandomImplementationProp, STRING, null, LOW, SslSecureRandomImplementationDoc) .define(SslSecureRandomImplementationProp, STRING, null, LOW, SslSecureRandomImplementationDoc)
.define(SslClientAuthProp, STRING, Defaults.SslClientAuthentication, in(Defaults.SslClientAuthenticationValidValues:_*), MEDIUM, SslClientAuthDoc) .define(SslClientAuthProp, STRING, Defaults.SslClientAuthentication, in(Defaults.SslClientAuthenticationValidValues:_*), MEDIUM, SslClientAuthDoc)
.define(SslCipherSuitesProp, LIST, Collections.emptyList(), MEDIUM, SslCipherSuitesDoc) .define(SslCipherSuitesProp, LIST, Collections.emptyList(), MEDIUM, SslCipherSuitesDoc)
.define(SslPrincipalMappingRulesProp, LIST, Defaults.SslPrincipalMappingRules, LOW, SslPrincipalMappingRulesDoc) .define(SslPrincipalMappingRulesProp, STRING, Defaults.SslPrincipalMappingRules, LOW, SslPrincipalMappingRulesDoc)
/** ********* Sasl Configuration ****************/ /** ********* Sasl Configuration ****************/
.define(SaslMechanismInterBrokerProtocolProp, STRING, Defaults.SaslMechanismInterBrokerProtocol, MEDIUM, SaslMechanismInterBrokerProtocolDoc) .define(SaslMechanismInterBrokerProtocolProp, STRING, Defaults.SaslMechanismInterBrokerProtocol, MEDIUM, SaslMechanismInterBrokerProtocolDoc)