mirror of https://github.com/apache/kafka.git
				
				
				
			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:
		
							parent
							
								
									364794866f
								
							
						
					
					
						commit
						88d1b6de1f
					
				|  | @ -55,7 +55,7 @@ public class BrokerSecurityConfigs { | |||
|             " 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>" + | ||||
|            " 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 " + | ||||
|             "names to short names (typically operating system usernames). The rules are evaluated in order and the " + | ||||
|  |  | |||
|  | @ -35,7 +35,6 @@ import java.net.InetAddress; | |||
| import java.net.InetSocketAddress; | ||||
| import java.nio.channels.SelectionKey; | ||||
| import java.nio.channels.SocketChannel; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Set; | ||||
| import java.util.function.Supplier; | ||||
|  | @ -63,8 +62,7 @@ public class SslChannelBuilder implements ChannelBuilder, ListenerReconfigurable | |||
|     public void configure(Map<String, ?> configs) throws KafkaException { | ||||
|         try { | ||||
|             this.configs = configs; | ||||
|             @SuppressWarnings("unchecked") | ||||
|             List<String> sslPrincipalMappingRules = (List<String>) configs.get(BrokerSecurityConfigs.SSL_PRINCIPAL_MAPPING_RULES_CONFIG); | ||||
|             String sslPrincipalMappingRules = (String) configs.get(BrokerSecurityConfigs.SSL_PRINCIPAL_MAPPING_RULES_CONFIG); | ||||
|             if (sslPrincipalMappingRules != null) | ||||
|                 sslPrincipalMapper = SslPrincipalMapper.fromRules(sslPrincipalMappingRules); | ||||
|             this.sslFactory = new SslFactory(mode, null, isInterBrokerListener); | ||||
|  |  | |||
|  | @ -18,53 +18,44 @@ package org.apache.kafka.common.security.ssl; | |||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.List; | ||||
| import java.util.Collections; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Locale; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
| 
 | ||||
| import static org.apache.kafka.common.config.internals.BrokerSecurityConfigs.DEFAULT_SSL_PRINCIPAL_MAPPING_RULES; | ||||
| 
 | ||||
| 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; | ||||
| 
 | ||||
|     public SslPrincipalMapper(List<Rule> sslPrincipalMappingRules) { | ||||
|         this.rules = sslPrincipalMappingRules; | ||||
|     public SslPrincipalMapper(String sslPrincipalMappingRules) { | ||||
|         this.rules = parseRules(splitRules(sslPrincipalMappingRules)); | ||||
|     } | ||||
| 
 | ||||
|     public static SslPrincipalMapper fromRules(List<String> sslPrincipalMappingRules) { | ||||
|         List<String> rules = sslPrincipalMappingRules == null ? Collections.singletonList("DEFAULT") : sslPrincipalMappingRules; | ||||
|         return new SslPrincipalMapper(parseRules(rules)); | ||||
|     public static SslPrincipalMapper fromRules(String sslPrincipalMappingRules) { | ||||
|         return new SslPrincipalMapper(sslPrincipalMappingRules); | ||||
|     } | ||||
| 
 | ||||
|     private static List<String> joinSplitRules(List<String> rules) { | ||||
|         String rule = "RULE:"; | ||||
|         String defaultRule = "DEFAULT"; | ||||
|         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); | ||||
|             } | ||||
|     private static List<String> splitRules(String sslPrincipalMappingRules) { | ||||
|         if (sslPrincipalMappingRules == null) { | ||||
|             sslPrincipalMappingRules = DEFAULT_SSL_PRINCIPAL_MAPPING_RULES; | ||||
|         } | ||||
|         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) { | ||||
|         rules = joinSplitRules(rules); | ||||
|         List<Rule> result = new ArrayList<>(); | ||||
|         for (String rule : rules) { | ||||
|             Matcher matcher = RULE_PARSER.matcher(rule); | ||||
|  | @ -74,15 +65,18 @@ public class SslPrincipalMapper { | |||
|             if (rule.length() != 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()); | ||||
|             } else { | ||||
|                 result.add(new Rule(matcher.group(5), | ||||
|                                     matcher.group(6), | ||||
|                                     "L".equals(matcher.group(7)), | ||||
|                                     "U".equals(matcher.group(7)))); | ||||
|             } else if (matcher.group(2) != null) { | ||||
|                 result.add(new Rule(matcher.group(2), | ||||
|                                     matcher.group(4), | ||||
|                                     "L".equals(matcher.group(6)), | ||||
|                                     "U".equals(matcher.group(6)))); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -30,8 +30,6 @@ import javax.net.ssl.SSLSession; | |||
| import javax.security.sasl.SaslServer; | ||||
| import java.net.InetAddress; | ||||
| import java.security.Principal; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| 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("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=(.*),O=(.*),L=(.*),ST=(.*),C=(.*)$/$1@$2/L", | ||||
|             "RULE:^.*[Cc][Nn]=([a-zA-Z0-9.]*).*$/$1/U", | ||||
|  |  | |||
|  | @ -18,9 +18,6 @@ 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; | ||||
| 
 | ||||
|  | @ -28,59 +25,37 @@ 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")); | ||||
|         testValidRule("DEFAULT"); | ||||
|         testValidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/"); | ||||
|         testValidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/L, DEFAULT"); | ||||
|         testValidRule("RULE:^CN=(.*?),OU=(.*?),O=(.*?),L=(.*?),ST=(.*?),C=(.*?)$/$1@$2/"); | ||||
|         testValidRule("RULE:^.*[Cc][Nn]=([a-zA-Z0-9.]*).*$/$1/L"); | ||||
|         testValidRule("RULE:^cn=(.?),ou=(.?),dc=(.?),dc=(.?)$/$1@$2/U"); | ||||
| 
 | ||||
|         testValidRule("RULE:^CN=([^,ADEFLTU,]+)(,.*|$)/$1/"); | ||||
|         testValidRule("RULE:^CN=([^,DEFAULT,]+)(,.*|$)/$1/"); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     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) { | ||||
|     private void testValidRule(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("default"); | ||||
|         testInvalidRule("DEFAUL"); | ||||
|         testInvalidRule("DEFAULT/L"); | ||||
|         testInvalidRule("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")); | ||||
|         testInvalidRule("RULE:CN=(.*?),OU=ServiceUsers.*/$1"); | ||||
|         testInvalidRule("rule:^CN=(.*?),OU=ServiceUsers.*$/$1/"); | ||||
|         testInvalidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/L/U"); | ||||
|         testInvalidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/L"); | ||||
|         testInvalidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/U"); | ||||
|         testInvalidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/LU"); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     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) { | ||||
|     private void testInvalidRule(String rules) { | ||||
|         try { | ||||
|             System.out.println(SslPrincipalMapper.fromRules(rules)); | ||||
|             fail("should have thrown IllegalArgumentException"); | ||||
|  | @ -90,7 +65,7 @@ public class SslPrincipalMapperTest { | |||
| 
 | ||||
|     @Test | ||||
|     public void testSslPrincipalMapper() throws Exception { | ||||
|         List<String> rules = Arrays.asList( | ||||
|         String rules = String.join(", ", | ||||
|             "RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/L", | ||||
|             "RULE:^CN=(.*?),OU=(.*?),O=(.*?),L=(.*?),ST=(.*?),C=(.*?)$/$1@$2/L", | ||||
|             "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")); | ||||
|     } | ||||
| 
 | ||||
|     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")); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1059,7 +1059,7 @@ object KafkaConfig { | |||
|       .define(SslSecureRandomImplementationProp, STRING, null, LOW, SslSecureRandomImplementationDoc) | ||||
|       .define(SslClientAuthProp, STRING, Defaults.SslClientAuthentication, in(Defaults.SslClientAuthenticationValidValues:_*), MEDIUM, SslClientAuthDoc) | ||||
|       .define(SslCipherSuitesProp, LIST, Collections.emptyList(), MEDIUM, SslCipherSuitesDoc) | ||||
|       .define(SslPrincipalMappingRulesProp, LIST, Defaults.SslPrincipalMappingRules, LOW, SslPrincipalMappingRulesDoc) | ||||
|       .define(SslPrincipalMappingRulesProp, STRING, Defaults.SslPrincipalMappingRules, LOW, SslPrincipalMappingRulesDoc) | ||||
| 
 | ||||
|       /** ********* Sasl Configuration ****************/ | ||||
|       .define(SaslMechanismInterBrokerProtocolProp, STRING, Defaults.SaslMechanismInterBrokerProtocol, MEDIUM, SaslMechanismInterBrokerProtocolDoc) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue