Skip to content

Commit 042f094

Browse files
authored
Support Matcher group index for extracting ACL entity from SAN in X509 auth provider (#56)
In order to allow for greater flexibility in extracting a part of the string given in the SAN in the client certificate in X509AuthenticationProvider, this commit adds another JVM parameter for specifying Java regex Matcher Group index. X509AuthTest still has coverage of this logic. Also, 1) internal enum names have been renamed for better readability (spaced with underscores), and 2) the test case has been enhanced with a more complicated regex with a Matcher group index.
1 parent da1ec01 commit 042f094

File tree

2 files changed

+57
-29
lines changed

2 files changed

+57
-29
lines changed

zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import javax.security.auth.x500.X500Principal;
3232
import org.apache.zookeeper.KeeperException;
3333
import org.apache.zookeeper.common.ClientX509Util;
34-
import org.apache.zookeeper.common.X509Exception;
3534
import org.apache.zookeeper.common.X509Exception.KeyManagerException;
3635
import org.apache.zookeeper.common.X509Exception.TrustManagerException;
3736
import org.apache.zookeeper.common.X509Util;
@@ -61,12 +60,14 @@ public class X509AuthenticationProvider implements AuthenticationProvider {
6160
* The following System Property keys are used to extract clientId from the client cert.
6261
* @see #getClientId(X509Certificate)
6362
*/
64-
public static final String ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENTCERTIDTYPE = "zookeeper.X509AuthenticationProvider.clientCertIdType";
65-
public static final String ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENTCERTIDSANMATCHTYPE = "zookeeper.X509AuthenticationProvider.clientCertIdSanMatchType";
63+
public static final String ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_TYPE = "zookeeper.X509AuthenticationProvider.clientCertIdType";
64+
public static final String ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_SAN_MATCH_TYPE = "zookeeper.X509AuthenticationProvider.clientCertIdSanMatchType";
6665
// Match Regex is used to choose which entry to use
67-
public static final String ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENTCERTIDSANMATCHREGEX = "zookeeper.X509AuthenticationProvider.clientCertIdSanMatchRegex";
66+
public static final String ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_SAN_MATCH_REGEX = "zookeeper.X509AuthenticationProvider.clientCertIdSanMatchRegex";
6867
// Extract Regex is used to construct a client ID (acl entity) to return
69-
public static final String ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENTCERTIDSANEXTRACTREGEX = "zookeeper.X509AuthenticationProvider.clientCertIdSanExtractRegex";
68+
public static final String ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_SAN_EXTRACT_REGEX = "zookeeper.X509AuthenticationProvider.clientCertIdSanExtractRegex";
69+
// Specifies match group index for the extract regex (i in Matcher.group(i))
70+
public static final String ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_SAN_EXTRACT_MATCHER_GROUP_INDEX = "zookeeper.X509AuthenticationProvider.clientCertIdSanExtractMatcherGroupIndex";
7071

7172
static final Logger LOG = LoggerFactory.getLogger(X509AuthenticationProvider.class);
7273
private final X509TrustManager trustManager;
@@ -196,11 +197,11 @@ public KeeperException.Code handleAuthentication(ServerCnxn cnxn, byte[] authDat
196197
*/
197198
protected String getClientId(X509Certificate clientCert) {
198199
String clientCertIdType =
199-
System.getProperty(ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENTCERTIDTYPE);
200+
System.getProperty(ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_TYPE);
200201
if (clientCertIdType != null && clientCertIdType.equalsIgnoreCase("SAN")) {
201202
try {
202203
return matchAndExtractSAN(clientCert);
203-
} catch (CertificateException | IllegalArgumentException ce) {
204+
} catch (Exception ce) {
204205
LOG.warn("X509AuthenticationProvider::getClientId(): failed to match and extract a"
205206
+ " client ID from SAN! Using Subject DN instead...", ce);
206207
}
@@ -212,14 +213,18 @@ protected String getClientId(X509Certificate clientCert) {
212213
private String matchAndExtractSAN(X509Certificate clientCert)
213214
throws CertificateParsingException {
214215
Integer matchType =
215-
Integer.getInteger(ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENTCERTIDSANMATCHTYPE);
216+
Integer.getInteger(ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_SAN_MATCH_TYPE);
216217
String matchRegex =
217-
System.getProperty(ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENTCERTIDSANMATCHREGEX);
218+
System.getProperty(ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_SAN_MATCH_REGEX);
218219
String extractRegex =
219-
System.getProperty(ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENTCERTIDSANEXTRACTREGEX);
220+
System.getProperty(
221+
ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_SAN_EXTRACT_REGEX);
222+
Integer extractMatcherGroupIndex = Integer.getInteger(
223+
ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_SAN_EXTRACT_MATCHER_GROUP_INDEX);
220224
LOG.info("X509AuthenticationProvider::matchAndExtractSAN(): Using SAN in the client cert "
221-
+ "for client ID! matchType: {}, matchRegex: {}, extractRegex: {}", matchType,
222-
matchRegex, extractRegex);
225+
+ "for client ID! matchType: {}, matchRegex: {}, extractRegex: {}, "
226+
+ "extractMatcherGroupIndex: {}", matchType, matchRegex, extractRegex,
227+
extractMatcherGroupIndex);
223228
if (matchType == null || matchRegex == null || extractRegex == null || matchType < 0
224229
|| matchType > 8) {
225230
// SAN extension must be in the range of [0, 8].
@@ -259,9 +264,12 @@ private String matchAndExtractSAN(X509Certificate clientCert)
259264
Pattern extractPattern = Pattern.compile(extractRegex);
260265
Matcher matcher = extractPattern.matcher(matched.iterator().next().get(1).toString());
261266
if (matcher.find()) {
262-
String result = matcher.group();
267+
// If extractMatcherGroupIndex is not given, return the 1st index by default
268+
extractMatcherGroupIndex =
269+
extractMatcherGroupIndex == null ? 1 : extractMatcherGroupIndex;
270+
String result = matcher.group(extractMatcherGroupIndex);
263271
LOG.info("X509AuthenticationProvider::matchAndExtractSAN(): returning extracted "
264-
+ "client ID: {}", result);
272+
+ "client ID: {} using Matcher group index: {}", result, extractMatcherGroupIndex);
265273
return result;
266274
}
267275
String errStr = "X509AuthenticationProvider::matchAndExtractSAN(): failed to find an "

zookeeper-server/src/test/java/org/apache/zookeeper/test/X509AuthTest.java

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
import javax.net.ssl.X509KeyManager;
4444
import javax.net.ssl.X509TrustManager;
4545
import javax.security.auth.x500.X500Principal;
46+
47+
import com.google.common.annotations.VisibleForTesting;
4648
import org.apache.zookeeper.KeeperException;
4749
import org.apache.zookeeper.ZKTestCase;
4850
import org.apache.zookeeper.server.MockServerCnxn;
@@ -94,35 +96,52 @@ public void testUntrustedAuth() {
9496

9597
@Test
9698
public void testSANBasedAuth() {
99+
String clientCertIdType = "SAN";
100+
String clientCertIdSANMatchType = "6";
101+
// The following clientCertIdSANMatchRegex matches the entire SAN String
102+
String clientCertIdSANMatchRegex = ".*";
103+
// TEST_SAN_STR = "a:b:c(d;e;f)" in the test. The following clientCertIdSANExtractRegex
104+
// extracts the first element in the parentheses excluding "a:b:c(" and trailing ";*"
105+
String clientCertIdSANExtractRegex = "^a:b:c\\((.+);.+;.+\\)$";
106+
// The following clientCertIdSANExtractMatcherGroupIndex specifies the first index in the
107+
// Matcher group, which is "d"
108+
String clientCertIdSANExtractMatcherGroupIndex = "1";
109+
String expectedClientIdFromSANExtraction = "d";
110+
97111
// Set JVM properties to enable SAN-based client id extraction
98112
System.setProperty(
99-
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENTCERTIDTYPE,
100-
"SAN");
113+
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_TYPE,
114+
clientCertIdType);
101115
System.setProperty(
102-
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENTCERTIDSANMATCHTYPE,
103-
"6");
116+
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_SAN_MATCH_TYPE,
117+
clientCertIdSANMatchType);
104118
System.setProperty(
105-
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENTCERTIDSANMATCHREGEX,
106-
TestCertificate.TEST_SAN_STR);
119+
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_SAN_MATCH_REGEX,
120+
clientCertIdSANMatchRegex);
107121
System.setProperty(
108-
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENTCERTIDSANEXTRACTREGEX,
109-
".*");
122+
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_SAN_EXTRACT_REGEX,
123+
clientCertIdSANExtractRegex);
124+
System.setProperty(
125+
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_SAN_EXTRACT_MATCHER_GROUP_INDEX,
126+
clientCertIdSANExtractMatcherGroupIndex);
110127

111128
X509AuthenticationProvider provider = createProvider(clientCert);
112129
MockServerCnxn cnxn = new MockServerCnxn();
113130
cnxn.clientChain = new X509Certificate[]{clientCert};
114131
assertEquals(KeeperException.Code.OK, provider.handleAuthentication(cnxn, null));
115-
assertEquals(TestCertificate.TEST_SAN_STR, cnxn.getAuthInfo().get(0).getId());
132+
assertEquals(expectedClientIdFromSANExtraction, cnxn.getAuthInfo().get(0).getId());
116133

117-
// Remove JVM properties
134+
// Remove JVM properties so they don't interfere with other tests
135+
System.clearProperty(
136+
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_TYPE);
118137
System.clearProperty(
119-
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENTCERTIDTYPE);
138+
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_SAN_MATCH_TYPE);
120139
System.clearProperty(
121-
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENTCERTIDSANMATCHTYPE);
140+
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_SAN_MATCH_REGEX);
122141
System.clearProperty(
123-
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENTCERTIDSANMATCHREGEX);
142+
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_SAN_EXTRACT_REGEX);
124143
System.clearProperty(
125-
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENTCERTIDSANEXTRACTREGEX);
144+
X509AuthenticationProvider.ZOOKEEPER_X509AUTHENTICATIONPROVIDER_CLIENT_CERT_ID_SAN_EXTRACT_MATCHER_GROUP_INDEX);
126145
}
127146

128147
private static class TestPublicKey implements PublicKey {
@@ -144,7 +163,8 @@ public byte[] getEncoded() {
144163
}
145164

146165
private static class TestCertificate extends X509Certificate {
147-
static final String TEST_SAN_STR = "test_san_userPrincipal";
166+
@VisibleForTesting
167+
static final String TEST_SAN_STR = "a:b:c(d;e;f)";
148168
private byte[] encoded;
149169
private X500Principal principal;
150170
private PublicKey publicKey;

0 commit comments

Comments
 (0)