mirror of https://github.com/apache/kafka.git
KAFKA-2711; SaslClientAuthenticator no longer needs KerberosNameParser in constructor
Also refactor `KerberosNameParser` and `KerberosName` to make the code clearer and easier to use when `shortName` is not needed. Author: Ismael Juma <ismael@juma.me.uk> Reviewers: Jun Rao <junrao@gmail.com> Closes #390 from ijuma/kafka-2711
This commit is contained in:
parent
622730905d
commit
d9ae33d4c0
|
@ -21,7 +21,7 @@ import java.util.Map;
|
|||
import org.apache.kafka.common.config.SaslConfigs;
|
||||
import org.apache.kafka.common.security.JaasUtils;
|
||||
import org.apache.kafka.common.security.auth.PrincipalBuilder;
|
||||
import org.apache.kafka.common.security.kerberos.KerberosNameParser;
|
||||
import org.apache.kafka.common.security.kerberos.KerberosShortNamer;
|
||||
import org.apache.kafka.common.security.kerberos.LoginManager;
|
||||
import org.apache.kafka.common.security.authenticator.SaslClientAuthenticator;
|
||||
import org.apache.kafka.common.security.authenticator.SaslServerAuthenticator;
|
||||
|
@ -45,7 +45,7 @@ public class SaslChannelBuilder implements ChannelBuilder {
|
|||
private PrincipalBuilder principalBuilder;
|
||||
private SslFactory sslFactory;
|
||||
private Map<String, ?> configs;
|
||||
private KerberosNameParser kerberosNameParser;
|
||||
private KerberosShortNamer kerberosShortNamer;
|
||||
|
||||
public SaslChannelBuilder(Mode mode, LoginType loginType, SecurityProtocol securityProtocol) {
|
||||
this.mode = mode;
|
||||
|
@ -66,7 +66,10 @@ public class SaslChannelBuilder implements ChannelBuilder {
|
|||
} catch (Exception ke) {
|
||||
defaultRealm = "";
|
||||
}
|
||||
kerberosNameParser = new KerberosNameParser(defaultRealm, (List<String>) configs.get(SaslConfigs.SASL_KERBEROS_PRINCIPAL_TO_LOCAL_RULES));
|
||||
|
||||
List<String> principalToLocalRules = (List<String>) configs.get(SaslConfigs.SASL_KERBEROS_PRINCIPAL_TO_LOCAL_RULES);
|
||||
if (principalToLocalRules != null)
|
||||
kerberosShortNamer = KerberosShortNamer.fromUnparsedRules(defaultRealm, principalToLocalRules);
|
||||
|
||||
if (this.securityProtocol == SecurityProtocol.SASL_SSL) {
|
||||
this.sslFactory = new SslFactory(mode);
|
||||
|
@ -83,10 +86,10 @@ public class SaslChannelBuilder implements ChannelBuilder {
|
|||
TransportLayer transportLayer = buildTransportLayer(id, key, socketChannel);
|
||||
Authenticator authenticator;
|
||||
if (mode == Mode.SERVER)
|
||||
authenticator = new SaslServerAuthenticator(id, loginManager.subject(), kerberosNameParser);
|
||||
authenticator = new SaslServerAuthenticator(id, loginManager.subject(), kerberosShortNamer);
|
||||
else
|
||||
authenticator = new SaslClientAuthenticator(id, loginManager.subject(), loginManager.serviceName(),
|
||||
socketChannel.socket().getInetAddress().getHostName(), kerberosNameParser);
|
||||
socketChannel.socket().getInetAddress().getHostName());
|
||||
authenticator.configure(transportLayer, this.principalBuilder, this.configs);
|
||||
return new KafkaChannel(id, transportLayer, authenticator, maxReceiveSize);
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -48,7 +48,6 @@ import org.apache.kafka.common.security.auth.KafkaPrincipal;
|
|||
import org.apache.kafka.common.security.auth.PrincipalBuilder;
|
||||
import org.apache.kafka.common.KafkaException;
|
||||
|
||||
import org.apache.kafka.common.security.kerberos.KerberosNameParser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -64,7 +63,6 @@ public class SaslClientAuthenticator implements Authenticator {
|
|||
private final String servicePrincipal;
|
||||
private final String host;
|
||||
private final String node;
|
||||
private final KerberosNameParser kerberosNameParser;
|
||||
|
||||
// assigned in `configure`
|
||||
private SaslClient saslClient;
|
||||
|
@ -77,12 +75,11 @@ public class SaslClientAuthenticator implements Authenticator {
|
|||
|
||||
private SaslState saslState = SaslState.INITIAL;
|
||||
|
||||
public SaslClientAuthenticator(String node, Subject subject, String servicePrincipal, String host, KerberosNameParser kerberosNameParser) throws IOException {
|
||||
public SaslClientAuthenticator(String node, Subject subject, String servicePrincipal, String host) throws IOException {
|
||||
this.node = node;
|
||||
this.subject = subject;
|
||||
this.host = host;
|
||||
this.servicePrincipal = servicePrincipal;
|
||||
this.kerberosNameParser = kerberosNameParser;
|
||||
}
|
||||
|
||||
public void configure(TransportLayer transportLayer, PrincipalBuilder principalBuilder, Map<String, ?> configs) throws KafkaException {
|
||||
|
@ -91,7 +88,7 @@ public class SaslClientAuthenticator implements Authenticator {
|
|||
|
||||
// determine client principal from subject.
|
||||
Principal clientPrincipal = subject.getPrincipals().iterator().next();
|
||||
this.clientPrincipalName = kerberosNameParser.parse(clientPrincipal.getName()).toString();
|
||||
this.clientPrincipalName = clientPrincipal.getName();
|
||||
this.saslClient = createSaslClient();
|
||||
} catch (Exception e) {
|
||||
throw new KafkaException("Failed to configure SaslClientAuthenticator", e);
|
||||
|
|
|
@ -35,7 +35,7 @@ import javax.security.sasl.SaslException;
|
|||
|
||||
import org.apache.kafka.common.KafkaException;
|
||||
import org.apache.kafka.common.security.kerberos.KerberosName;
|
||||
import org.apache.kafka.common.security.kerberos.KerberosNameParser;
|
||||
import org.apache.kafka.common.security.kerberos.KerberosShortNamer;
|
||||
import org.ietf.jgss.GSSContext;
|
||||
import org.ietf.jgss.GSSCredential;
|
||||
import org.ietf.jgss.GSSException;
|
||||
|
@ -59,7 +59,7 @@ public class SaslServerAuthenticator implements Authenticator {
|
|||
private final SaslServer saslServer;
|
||||
private final Subject subject;
|
||||
private final String node;
|
||||
private final KerberosNameParser kerberosNameParser;
|
||||
private final KerberosShortNamer kerberosNamer;
|
||||
|
||||
// assigned in `configure`
|
||||
private TransportLayer transportLayer;
|
||||
|
@ -68,14 +68,14 @@ public class SaslServerAuthenticator implements Authenticator {
|
|||
private NetworkReceive netInBuffer;
|
||||
private NetworkSend netOutBuffer;
|
||||
|
||||
public SaslServerAuthenticator(String node, final Subject subject, KerberosNameParser kerberosNameParser) throws IOException {
|
||||
public SaslServerAuthenticator(String node, final Subject subject, KerberosShortNamer kerberosNameParser) throws IOException {
|
||||
if (subject == null)
|
||||
throw new IllegalArgumentException("subject cannot be null");
|
||||
if (subject.getPrincipals().isEmpty())
|
||||
throw new IllegalArgumentException("subject must have at least one principal");
|
||||
this.node = node;
|
||||
this.subject = subject;
|
||||
this.kerberosNameParser = kerberosNameParser;
|
||||
this.kerberosNamer = kerberosNameParser;
|
||||
saslServer = createSaslServer();
|
||||
}
|
||||
|
||||
|
@ -86,11 +86,11 @@ public class SaslServerAuthenticator implements Authenticator {
|
|||
private SaslServer createSaslServer() throws IOException {
|
||||
// server is using a JAAS-authenticated subject: determine service principal name and hostname from kafka server's subject.
|
||||
final SaslServerCallbackHandler saslServerCallbackHandler = new SaslServerCallbackHandler(
|
||||
Configuration.getConfiguration(), kerberosNameParser);
|
||||
Configuration.getConfiguration(), kerberosNamer);
|
||||
final Principal servicePrincipal = subject.getPrincipals().iterator().next();
|
||||
KerberosName kerberosName;
|
||||
try {
|
||||
kerberosName = kerberosNameParser.parse(servicePrincipal.getName());
|
||||
kerberosName = KerberosName.parse(servicePrincipal.getName());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new KafkaException("Principal has name with unexpected format " + servicePrincipal);
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ package org.apache.kafka.common.security.authenticator;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.kafka.common.security.kerberos.KerberosNameParser;
|
||||
import org.apache.kafka.common.security.kerberos.KerberosShortNamer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import javax.security.auth.callback.Callback;
|
||||
|
@ -36,13 +36,13 @@ import org.apache.kafka.common.security.JaasUtils;
|
|||
|
||||
public class SaslServerCallbackHandler implements CallbackHandler {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SaslServerCallbackHandler.class);
|
||||
private final KerberosNameParser kerberosNameParser;
|
||||
private final KerberosShortNamer kerberosShortNamer;
|
||||
|
||||
public SaslServerCallbackHandler(Configuration configuration, KerberosNameParser kerberosNameParser) throws IOException {
|
||||
public SaslServerCallbackHandler(Configuration configuration, KerberosShortNamer kerberosNameParser) throws IOException {
|
||||
AppConfigurationEntry[] configurationEntries = configuration.getAppConfigurationEntry(JaasUtils.LOGIN_CONTEXT_SERVER);
|
||||
if (configurationEntries == null)
|
||||
throw new IOException("Could not find a 'KafkaServer' entry in this configuration: Kafka Server cannot start.");
|
||||
this.kerberosNameParser = kerberosNameParser;
|
||||
this.kerberosShortNamer = kerberosNameParser;
|
||||
}
|
||||
|
||||
public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
|
||||
|
@ -68,9 +68,9 @@ public class SaslServerCallbackHandler implements CallbackHandler {
|
|||
authorizationID);
|
||||
ac.setAuthorized(true);
|
||||
|
||||
KerberosName kerberosName = kerberosNameParser.parse(authenticationID);
|
||||
KerberosName kerberosName = KerberosName.parse(authenticationID);
|
||||
try {
|
||||
String userName = kerberosName.shortName();
|
||||
String userName = kerberosShortNamer.shortName(kerberosName);
|
||||
LOG.info("Setting authorizedID: {}", userName);
|
||||
ac.setAuthorizedID(userName);
|
||||
} catch (IOException e) {
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* 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.kerberos;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class BadFormatString extends IOException {
|
||||
BadFormatString(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
BadFormatString(String msg, Throwable err) {
|
||||
super(msg, err);
|
||||
}
|
||||
}
|
|
@ -18,11 +18,16 @@
|
|||
|
||||
package org.apache.kafka.common.security.kerberos;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class KerberosName {
|
||||
|
||||
/**
|
||||
* A pattern that matches a Kerberos name with at most 3 components.
|
||||
*/
|
||||
private static final Pattern NAME_PARSER = Pattern.compile("([^/@]*)(/([^/@]*))?@([^/@]*)");
|
||||
|
||||
/** The first component of the name */
|
||||
private final String serviceName;
|
||||
/** The second component of the name. It may be null. */
|
||||
|
@ -30,19 +35,31 @@ public class KerberosName {
|
|||
/** The realm of the name. */
|
||||
private final String realm;
|
||||
|
||||
/* Rules for the translation of the principal name into an operating system name */
|
||||
private final List<KerberosRule> principalToLocalRules;
|
||||
|
||||
/**
|
||||
* Creates an instance of `KerberosName` with the provided parameters.
|
||||
*/
|
||||
public KerberosName(String serviceName, String hostName, String realm, List<KerberosRule> principalToLocalRules) {
|
||||
public KerberosName(String serviceName, String hostName, String realm) {
|
||||
if (serviceName == null)
|
||||
throw new IllegalArgumentException("serviceName must not be null");
|
||||
this.serviceName = serviceName;
|
||||
this.hostName = hostName;
|
||||
this.realm = realm;
|
||||
this.principalToLocalRules = principalToLocalRules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a name from the full Kerberos principal name.
|
||||
*/
|
||||
public static KerberosName parse(String principalName) {
|
||||
Matcher match = NAME_PARSER.matcher(principalName);
|
||||
if (!match.matches()) {
|
||||
if (principalName.contains("@")) {
|
||||
throw new IllegalArgumentException("Malformed Kerberos name: " + principalName);
|
||||
} else {
|
||||
return new KerberosName(principalName, null, null);
|
||||
}
|
||||
} else {
|
||||
return new KerberosName(match.group(1), match.group(3), match.group(4));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,28 +104,4 @@ public class KerberosName {
|
|||
return realm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the translation of the principal name into an operating system
|
||||
* user name.
|
||||
* @return the short name
|
||||
* @throws IOException
|
||||
*/
|
||||
public String shortName() throws IOException {
|
||||
String[] params;
|
||||
if (hostName == null) {
|
||||
// if it is already simple, just return it
|
||||
if (realm == null)
|
||||
return serviceName;
|
||||
params = new String[]{realm, serviceName};
|
||||
} else {
|
||||
params = new String[]{realm, serviceName, hostName};
|
||||
}
|
||||
for (KerberosRule r : principalToLocalRules) {
|
||||
String result = r.apply(params);
|
||||
if (result != null)
|
||||
return result;
|
||||
}
|
||||
throw new NoMatchingRule("No rules applied to " + toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -108,10 +108,10 @@ class KerberosRule {
|
|||
* @param format the string to replace parameters again
|
||||
* @param params the list of parameters
|
||||
* @return the generated string with the parameter references replaced.
|
||||
* @throws KerberosNameParser.BadFormatString
|
||||
* @throws BadFormatString
|
||||
*/
|
||||
static String replaceParameters(String format,
|
||||
String[] params) throws KerberosNameParser.BadFormatString {
|
||||
String[] params) throws BadFormatString {
|
||||
Matcher match = PARAMETER_PATTERN.matcher(format);
|
||||
int start = 0;
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
@ -122,13 +122,13 @@ class KerberosRule {
|
|||
try {
|
||||
int num = Integer.parseInt(paramNum);
|
||||
if (num < 0 || num > params.length) {
|
||||
throw new KerberosNameParser.BadFormatString("index " + num + " from " + format +
|
||||
throw new BadFormatString("index " + num + " from " + format +
|
||||
" is outside of the valid range 0 to " +
|
||||
(params.length - 1));
|
||||
}
|
||||
result.append(params[num]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new KerberosNameParser.BadFormatString("bad format in username mapping in " +
|
||||
throw new BadFormatString("bad format in username mapping in " +
|
||||
paramNum, nfe);
|
||||
}
|
||||
|
||||
|
|
|
@ -30,42 +30,23 @@ import java.util.regex.Pattern;
|
|||
* particular, it splits them apart and translates them down into local
|
||||
* operating system names.
|
||||
*/
|
||||
public class KerberosNameParser {
|
||||
|
||||
/**
|
||||
* A pattern that matches a Kerberos name with at most 3 components.
|
||||
*/
|
||||
private static final Pattern NAME_PARSER = Pattern.compile("([^/@]*)(/([^/@]*))?@([^/@]*)");
|
||||
public class KerberosShortNamer {
|
||||
|
||||
/**
|
||||
* A pattern for parsing a auth_to_local rule.
|
||||
*/
|
||||
private static final Pattern RULE_PARSER = Pattern.compile("((DEFAULT)|(RULE:\\[(\\d*):([^\\]]*)](\\(([^)]*)\\))?(s/([^/]*)/([^/]*)/(g)?)?))");
|
||||
|
||||
/**
|
||||
* The list of translation rules.
|
||||
*/
|
||||
/* Rules for the translation of the principal name into an operating system name */
|
||||
private final List<KerberosRule> principalToLocalRules;
|
||||
|
||||
public KerberosNameParser(String defaultRealm, List<String> principalToLocalRules) {
|
||||
List<String> rules = principalToLocalRules == null ? Collections.singletonList("DEFAULT") : principalToLocalRules;
|
||||
this.principalToLocalRules = parseRules(defaultRealm, rules);
|
||||
public KerberosShortNamer(List<KerberosRule> principalToLocalRules) {
|
||||
this.principalToLocalRules = principalToLocalRules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a name from the full Kerberos principal name.
|
||||
*/
|
||||
public KerberosName parse(String principalName) {
|
||||
Matcher match = NAME_PARSER.matcher(principalName);
|
||||
if (!match.matches()) {
|
||||
if (principalName.contains("@")) {
|
||||
throw new IllegalArgumentException("Malformed Kerberos name: " + principalName);
|
||||
} else {
|
||||
return new KerberosName(principalName, null, null, principalToLocalRules);
|
||||
}
|
||||
} else {
|
||||
return new KerberosName(match.group(1), match.group(3), match.group(4), principalToLocalRules);
|
||||
}
|
||||
public static KerberosShortNamer fromUnparsedRules(String defaultRealm, List<String> principalToLocalRules) {
|
||||
List<String> rules = principalToLocalRules == null ? Collections.singletonList("DEFAULT") : principalToLocalRules;
|
||||
return new KerberosShortNamer(parseRules(defaultRealm, rules));
|
||||
}
|
||||
|
||||
private static List<KerberosRule> parseRules(String defaultRealm, List<String> rules) {
|
||||
|
@ -93,13 +74,28 @@ public class KerberosNameParser {
|
|||
return result;
|
||||
}
|
||||
|
||||
public static class BadFormatString extends IOException {
|
||||
BadFormatString(String msg) {
|
||||
super(msg);
|
||||
/**
|
||||
* Get the translation of the principal name into an operating system
|
||||
* user name.
|
||||
* @return the short name
|
||||
* @throws IOException
|
||||
*/
|
||||
public String shortName(KerberosName kerberosName) throws IOException {
|
||||
String[] params;
|
||||
if (kerberosName.hostName() == null) {
|
||||
// if it is already simple, just return it
|
||||
if (kerberosName.realm() == null)
|
||||
return kerberosName.serviceName();
|
||||
params = new String[]{kerberosName.realm(), kerberosName.serviceName()};
|
||||
} else {
|
||||
params = new String[]{kerberosName.realm(), kerberosName.serviceName(), kerberosName.hostName()};
|
||||
}
|
||||
BadFormatString(String msg, Throwable err) {
|
||||
super(msg, err);
|
||||
for (KerberosRule r : principalToLocalRules) {
|
||||
String result = r.apply(params);
|
||||
if (result != null)
|
||||
return result;
|
||||
}
|
||||
throw new NoMatchingRule("No rules applied to " + toString());
|
||||
}
|
||||
|
||||
}
|
|
@ -36,24 +36,24 @@ public class KerberosNameTest {
|
|||
"RULE:[2:$1](App\\..*)s/App\\.(.*)/$1/g",
|
||||
"DEFAULT"
|
||||
));
|
||||
KerberosNameParser parser = new KerberosNameParser("REALM.COM", rules);
|
||||
KerberosShortNamer shortNamer = KerberosShortNamer.fromUnparsedRules("REALM.COM", rules);
|
||||
|
||||
KerberosName name = parser.parse("App.service-name/example.com@REALM.COM");
|
||||
KerberosName name = KerberosName.parse("App.service-name/example.com@REALM.COM");
|
||||
assertEquals("App.service-name", name.serviceName());
|
||||
assertEquals("example.com", name.hostName());
|
||||
assertEquals("REALM.COM", name.realm());
|
||||
assertEquals("service-name", name.shortName());
|
||||
assertEquals("service-name", shortNamer.shortName(name));
|
||||
|
||||
name = parser.parse("App.service-name@REALM.COM");
|
||||
name = KerberosName.parse("App.service-name@REALM.COM");
|
||||
assertEquals("App.service-name", name.serviceName());
|
||||
assertNull(name.hostName());
|
||||
assertEquals("REALM.COM", name.realm());
|
||||
assertEquals("service-name", name.shortName());
|
||||
assertEquals("service-name", shortNamer.shortName(name));
|
||||
|
||||
name = parser.parse("user/host@REALM.COM");
|
||||
name = KerberosName.parse("user/host@REALM.COM");
|
||||
assertEquals("user", name.serviceName());
|
||||
assertEquals("host", name.hostName());
|
||||
assertEquals("REALM.COM", name.realm());
|
||||
assertEquals("user", name.shortName());
|
||||
assertEquals("user", shortNamer.shortName(name));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue