PKI: allow username extraction to work for certs with CN only

Our current CA configuration creates certificates with only a CN and this caused
the regular expression in the PKI realm to fail. The default regular expression in
the PKI realm has been changed to allow for only a CN, while still maintaining the
ability to extract only the CN when other fields are present in the DN.

Additionally, the CA configuration has been updated so that is will copy any of the
originally specified fields (besides CN and EMAILADDRESS) over to the signed
certificate.

Original commit: elastic/x-pack-elasticsearch@ff27f69781
This commit is contained in:
jaymode 2015-06-04 09:23:52 -04:00
parent 7c62e4c82c
commit 6f079dd2f2
2 changed files with 51 additions and 4 deletions

View File

@ -5,6 +5,7 @@
*/
package org.elasticsearch.shield.authc.pki;
import org.elasticsearch.common.base.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.settings.Settings;
@ -39,6 +40,7 @@ public class PkiRealm extends Realm<X509AuthenticationToken> {
public static final String PKI_CERT_HEADER_NAME = "__SHIELD_CLIENT_CERTIFICATE";
public static final String TYPE = "pki";
public static final String DEFAULT_USERNAME_PATTERN = "CN=(.*?)(?:,|$)";
// For client based cert validation, the auth type must be specified but UNKNOWN is an acceptable value
public static final String AUTH_TYPE = "UNKNOWN";
@ -50,7 +52,7 @@ public class PkiRealm extends Realm<X509AuthenticationToken> {
public PkiRealm(RealmConfig config, DnRoleMapper roleMapper) {
super(TYPE, config);
this.trustManagers = trustManagers(config.settings(), config.env());
this.principalPattern = Pattern.compile(config.settings().get("username_pattern", "CN=(.*?),"), Pattern.CASE_INSENSITIVE);
this.principalPattern = Pattern.compile(config.settings().get("username_pattern", DEFAULT_USERNAME_PATTERN), Pattern.CASE_INSENSITIVE);
this.roleMapper = roleMapper;
checkSSLEnabled(config, logger);
}
@ -91,7 +93,7 @@ public class PkiRealm extends Realm<X509AuthenticationToken> {
return null;
}
String dn = certificates[0].getSubjectX500Principal().getName();
String dn = certificates[0].getSubjectX500Principal().toString();
Matcher matcher = principalPattern.matcher(dn);
if (!matcher.find()) {
if (logger.isDebugEnabled()) {
@ -101,6 +103,12 @@ public class PkiRealm extends Realm<X509AuthenticationToken> {
}
String principal = matcher.group(1);
if (Strings.isNullOrEmpty(principal)) {
if (logger.isDebugEnabled()) {
logger.debug("certificate authentication succeeded for [{}] but extracted principal was empty", dn);
}
return null;
}
return new X509AuthenticationToken(certificates, principal, dn);
}

View File

@ -13,18 +13,21 @@ import org.elasticsearch.shield.authc.RealmConfig;
import org.elasticsearch.shield.authc.support.DnRoleMapper;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.shield.support.NoOpLogger;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.test.rest.FakeRestRequest;
import org.elasticsearch.transport.TransportMessage;
import org.junit.Before;
import org.junit.Test;
import javax.security.auth.x500.X500Principal;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.regex.Pattern;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
@ -160,6 +163,42 @@ public class PkiRealmTests extends ElasticsearchTestCase {
}
}
@Test
public void certificateWithOnlyCnExtractsProperly() throws Exception {
X509Certificate certificate = mock(X509Certificate.class);
X500Principal principal = new X500Principal("CN=PKI Client");
when(certificate.getSubjectX500Principal()).thenReturn(principal);
X509AuthenticationToken token = PkiRealm.token(new X509Certificate[]{certificate}, Pattern.compile(PkiRealm.DEFAULT_USERNAME_PATTERN), NoOpLogger.INSTANCE);
assertThat(token, notNullValue());
assertThat(token.principal(), is("PKI Client"));
assertThat(token.dn(), is("CN=PKI Client"));
}
@Test
public void certificateWithCnAndOuExtractsProperly() throws Exception {
X509Certificate certificate = mock(X509Certificate.class);
X500Principal principal = new X500Principal("CN=PKI Client, OU=Shield");
when(certificate.getSubjectX500Principal()).thenReturn(principal);
X509AuthenticationToken token = PkiRealm.token(new X509Certificate[]{certificate}, Pattern.compile(PkiRealm.DEFAULT_USERNAME_PATTERN), NoOpLogger.INSTANCE);
assertThat(token, notNullValue());
assertThat(token.principal(), is("PKI Client"));
assertThat(token.dn(), is("CN=PKI Client, OU=Shield"));
}
@Test
public void certificateWithCnInMiddle() throws Exception {
X509Certificate certificate = mock(X509Certificate.class);
X500Principal principal = new X500Principal("EMAILADDRESS=pki@elastic.co, CN=PKI Client, OU=Shield");
when(certificate.getSubjectX500Principal()).thenReturn(principal);
X509AuthenticationToken token = PkiRealm.token(new X509Certificate[]{certificate}, Pattern.compile(PkiRealm.DEFAULT_USERNAME_PATTERN), NoOpLogger.INSTANCE);
assertThat(token, notNullValue());
assertThat(token.principal(), is("PKI Client"));
assertThat(token.dn(), is("EMAILADDRESS=pki@elastic.co, CN=PKI Client, OU=Shield"));
}
static X509Certificate readCert(Path path) throws Exception {
try (InputStream in = Files.newInputStream(path)) {
CertificateFactory factory = CertificateFactory.getInstance("X.509");