Skip to content

Commit c36f0e1

Browse files
authored
Create X509AuthenticationConfig (#64)
This commit creates a X509AuthenticationConfig class as a central place for all the (except zookeeper.X509AuthenticationProvider.superUser to minimize changes on upstream zookeeper code) x509 authentication related config properties, including (1)keys (2)setters (3)getters for those properties. X509AuthenticationConfig enables reading configs from 1) the zoo.cfg file (by way of QuorumPeerConfig) and 2) JVM argument (System Properties) for backward-compatibility.
1 parent 3bc2e79 commit c36f0e1

File tree

10 files changed

+400
-229
lines changed

10 files changed

+400
-229
lines changed

zookeeper-server/src/main/java/org/apache/zookeeper/server/PrepRequestProcessor.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
import org.apache.zookeeper.server.ZooKeeperServer.PrecalculatedDigest;
6060
import org.apache.zookeeper.server.auth.ProviderRegistry;
6161
import org.apache.zookeeper.server.auth.ServerAuthenticationProvider;
62-
import org.apache.zookeeper.server.auth.znode.groupacl.ZNodeGroupAclProperties;
62+
import org.apache.zookeeper.server.auth.X509AuthenticationConfig;
6363
import org.apache.zookeeper.server.quorum.LeaderZooKeeperServer;
6464
import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer;
6565
import org.apache.zookeeper.server.quorum.QuorumPeerConfig;
@@ -1011,11 +1011,11 @@ public static List<ACL> fixupACL(String path, List<Id> authInfo, List<ACL> acls)
10111011
if (id == null || id.getScheme() == null) {
10121012
throw new KeeperException.InvalidACLException(path);
10131013
}
1014-
if (id.getScheme().equals("world") && id.getId().equals("anyone") && !QuorumPeerConfig
1015-
.isSetX509ClientIdAsAclEnabled()) {
1014+
if (id.getScheme().equals("world") && id.getId().equals("anyone") && !X509AuthenticationConfig
1015+
.getInstance().isX509ClientIdAsAclEnabled()) {
10161016
rv.add(a);
1017-
} else if (id.getScheme().equals("auth") || QuorumPeerConfig
1018-
.isSetX509ClientIdAsAclEnabled()) {
1017+
} else if (id.getScheme().equals("auth") || X509AuthenticationConfig
1018+
.getInstance().isX509ClientIdAsAclEnabled()) {
10191019
// This is the "auth" id, so we have to expand it to the
10201020
// authenticated ids of the requestor
10211021
boolean authIdValid = false;
@@ -1029,7 +1029,7 @@ public static List<ACL> fixupACL(String path, List<Id> authInfo, List<ACL> acls)
10291029
}
10301030
}
10311031
// If the znode path contains open read access node path prefix, add (world:anyone, r)
1032-
if (ZNodeGroupAclProperties.getInstance().getOpenReadAccessPathPrefixes().stream()
1032+
if (X509AuthenticationConfig.getInstance().getZnodeGroupAclOpenReadAccessPathPrefixes().stream()
10331033
.anyMatch(path::startsWith)) {
10341034
rv.add(new ACL(ZooDefs.Perms.READ, ZooDefs.Ids.ANYONE_ID_UNSAFE));
10351035
}
Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.zookeeper.server.auth;
20+
21+
import java.util.Arrays;
22+
import java.util.Collections;
23+
import java.util.Set;
24+
import java.util.stream.Collectors;
25+
import com.google.common.annotations.VisibleForTesting;
26+
import org.slf4j.Logger;
27+
import org.slf4j.LoggerFactory;
28+
29+
/**
30+
* This class contains config properties for x509-based authentication
31+
*/
32+
public class X509AuthenticationConfig {
33+
private static final Logger LOG = LoggerFactory.getLogger(X509AuthenticationConfig.class);
34+
private static X509AuthenticationConfig instance = null;
35+
private static final String LOG_MSG_PREFIX = X509AuthenticationConfig.class.getName() + "::";
36+
37+
private X509AuthenticationConfig() {
38+
}
39+
40+
public static X509AuthenticationConfig getInstance() {
41+
if (instance == null) {
42+
synchronized (X509AuthenticationConfig.class) {
43+
if (instance == null) {
44+
instance = new X509AuthenticationConfig();
45+
}
46+
}
47+
}
48+
return instance;
49+
}
50+
51+
/**
52+
* The following System Property keys are used to extract clientId from the client cert.
53+
*/
54+
public static final String SSL_X509_CONFIG_PREFIX = "zookeeper.ssl.x509.";
55+
public static final String SSL_X509_CLIENT_CERT_ID_TYPE =
56+
SSL_X509_CONFIG_PREFIX + "clientCertIdType";
57+
public static final String SSL_X509_CLIENT_CERT_ID_SAN_MATCH_TYPE =
58+
SSL_X509_CONFIG_PREFIX + "clientCertIdSanMatchType";
59+
// Match Regex is used to choose which entry to use, default value ".*"
60+
public static final String SSL_X509_CLIENT_CERT_ID_SAN_MATCH_REGEX =
61+
SSL_X509_CONFIG_PREFIX + "clientCertIdSanMatchRegex";
62+
// Extract Regex is used to construct a client ID (acl entity) to return, default value ".*"
63+
public static final String SSL_X509_CLIENT_CERT_ID_SAN_EXTRACT_REGEX =
64+
SSL_X509_CONFIG_PREFIX + "clientCertIdSanExtractRegex";
65+
// Specifies match group index for the extract regex (i in Matcher.group(i)), default value 0
66+
public static final String SSL_X509_CLIENT_CERT_ID_SAN_EXTRACT_MATCHER_GROUP_INDEX =
67+
SSL_X509_CONFIG_PREFIX + "clientCertIdSanExtractMatcherGroupIndex";
68+
public static final String SUBJECT_ALTERNATIVE_NAME_SHORT = "SAN";
69+
private static final String DEFAULT_REGEX = ".*";
70+
private String clientCertIdType;
71+
private int clientCertIdSanMatchType = -1;
72+
private String clientCertIdSanMatchRegex;
73+
private String clientCertIdSanExtractRegex;
74+
private int clientCertIdSanExtractMatcherGroupIndex = -1;
75+
76+
/** ZooKeeper server-side ZNode group ACL feature */
77+
// x509ClientIdAsAclEnabled enables/disables whether znodes created by auth'ed clients
78+
// should have ACL fields populated with the client Id given by the authentication provider.
79+
// Has the same effect as the ZK client using ZooDefs.Ids.CREATOR_ALL_ACL.
80+
public static final String ZNODE_GROUP_ACL_CONFIG_PREFIX = "zookeeper.X509ZNodeGroupAclProvider.";
81+
// Enables/disables whether znodes created by auth'ed clients
82+
// should have ACL fields populated with the client Id given by the authentication provider.
83+
// Has the same effect as the ZK client using ZooDefs.Ids.CREATOR_ALL_ACL.
84+
public static final String SET_X509_CLIENT_ID_AS_ACL =
85+
ZNODE_GROUP_ACL_CONFIG_PREFIX + "setX509ClientIdAsAcl";
86+
// A list of domain names that will have cross-domain access privilege, separated by ","
87+
public static final String CROSS_DOMAIN_ACCESS_DOMAIN_NAME =
88+
ZNODE_GROUP_ACL_CONFIG_PREFIX + "crossDomainAccessDomainName";
89+
// A defined URI represents a super user, same concept as the original x509 superuser
90+
public static final String ZOOKEEPER_ZNODEGROUPACL_SUPERUSER_ID =
91+
ZNODE_GROUP_ACL_CONFIG_PREFIX + "superUserId";
92+
// A list of znode path prefixes, separated by ","
93+
// Znode whose path starts with the defined path prefix would have open read access
94+
// Meaning the znode will have (world:anyone, r) ACL
95+
public static final String OPEN_READ_ACCESS_PATH_PREFIX =
96+
ZNODE_GROUP_ACL_CONFIG_PREFIX + "openReadAccessPathPrefix";
97+
// If the server is dedicated for one domain, use this config property to define the domain name,
98+
// and enable connection filtering feature for this domain
99+
public static final String DEDICATED_DOMAIN = ZNODE_GROUP_ACL_CONFIG_PREFIX + "dedicatedDomain";
100+
public static final String CLIENT_URI_DOMAIN_MAPPING_ROOT_PATH =
101+
ZNODE_GROUP_ACL_CONFIG_PREFIX + "clientUriDomainMappingRootPath";
102+
103+
private String x509ClientIdAsAclEnabled;
104+
private String znodeGroupAclSuperUserId;
105+
private String znodeGroupAclCrossDomainAccessDomainNameStr;
106+
private String znodeGroupAclOpenReadAccessPathPrefixStr;
107+
private String znodeGroupAclServerDedicatedDomain;
108+
private String znodeGroupAclClientUriDomainMappingRootPath;
109+
// Although using "volatile" keyword with double checked locking could prevent the undesired
110+
//creation of multiple objects; not using here for the consideration of read performance
111+
private Set<String> openReadAccessPathPrefixes;
112+
private Set<String> crossDomainAccessDomains;
113+
private final Object openReadAccessPathPrefixesLock = new Object();
114+
private final Object crossDomainAccessDomainsLock = new Object();
115+
116+
/**
117+
* Setters for X509 properties
118+
*/
119+
public void setClientCertIdType(String clientCertIdType) {
120+
this.clientCertIdType = clientCertIdType;
121+
}
122+
123+
public void setClientCertIdSanMatchType(String clientCertIdSanMatchType) {
124+
if (clientCertIdSanMatchType == null) {
125+
return;
126+
}
127+
try {
128+
this.clientCertIdSanMatchType = Integer.parseInt(clientCertIdSanMatchType);
129+
} catch (NumberFormatException e) {
130+
String errMsg =
131+
LOG_MSG_PREFIX + "Could not parse number for clientCertIdSanMatchType, provided value: "
132+
+ clientCertIdSanMatchType;
133+
LOG.error(errMsg);
134+
throw new IllegalArgumentException(errMsg);
135+
}
136+
}
137+
138+
public void setClientCertIdSanMatchRegex(String clientCertIdSanMatchRegex) {
139+
this.clientCertIdSanMatchRegex = clientCertIdSanMatchRegex;
140+
}
141+
142+
public void setClientCertIdSanExtractRegex(String clientCertIdSanExtractRegex) {
143+
this.clientCertIdSanExtractRegex = clientCertIdSanExtractRegex;
144+
}
145+
146+
public void setClientCertIdSanExtractMatcherGroupIndex(
147+
String clientCertIdSanExtractMatcherGroupIndex) {
148+
if (clientCertIdSanExtractMatcherGroupIndex == null) {
149+
return;
150+
}
151+
try {
152+
this.clientCertIdSanExtractMatcherGroupIndex =
153+
Integer.parseInt(clientCertIdSanExtractMatcherGroupIndex);
154+
} catch (NumberFormatException e) {
155+
String errMsg = LOG_MSG_PREFIX
156+
+ "Could not parse number for clientCertIdSanExtractMatcherGroupIndex, provided value: "
157+
+ clientCertIdSanExtractMatcherGroupIndex;
158+
LOG.error(errMsg);
159+
throw new IllegalArgumentException(errMsg);
160+
}
161+
}
162+
163+
/**
164+
* Setters for X509 Znode Group Acl properties
165+
*/
166+
public void setX509ClientIdAsAclEnabled(String enabled) {
167+
x509ClientIdAsAclEnabled = enabled;
168+
}
169+
170+
public void setZnodeGroupAclSuperUserId(String znodeGroupAclSuperUserId) {
171+
this.znodeGroupAclSuperUserId = znodeGroupAclSuperUserId;
172+
}
173+
174+
public void setZnodeGroupAclCrossDomainAccessDomainNameStr(String znodeGroupAclCrossDomainAccessDomainNameStr) {
175+
this.znodeGroupAclCrossDomainAccessDomainNameStr = znodeGroupAclCrossDomainAccessDomainNameStr;
176+
}
177+
178+
public void setZnodeGroupAclOpenReadAccessPathPrefixStr(
179+
String znodeGroupAclOpenReadAccessPathPrefixStr) {
180+
this.znodeGroupAclOpenReadAccessPathPrefixStr = znodeGroupAclOpenReadAccessPathPrefixStr;
181+
}
182+
183+
public void setZnodeGroupAclServerDedicatedDomain(String znodeGroupAclServerDedicatedDomain) {
184+
this.znodeGroupAclServerDedicatedDomain = znodeGroupAclServerDedicatedDomain;
185+
}
186+
187+
public void setZnodeGroupAclClientUriDomainMappingRootPath(
188+
String znodeGroupAclClientUriDomainMappingRootPath) {
189+
this.znodeGroupAclClientUriDomainMappingRootPath = znodeGroupAclClientUriDomainMappingRootPath;
190+
}
191+
192+
/**
193+
* Getters for X509 properties
194+
*/
195+
public String getClientCertIdType() {
196+
if (clientCertIdType == null) {
197+
setClientCertIdType(System.getProperty(SSL_X509_CLIENT_CERT_ID_TYPE));
198+
}
199+
return clientCertIdType;
200+
}
201+
202+
public int getClientCertIdSanMatchType() {
203+
if (clientCertIdSanMatchType == -1) {
204+
setClientCertIdSanMatchType(System.getProperty(SSL_X509_CLIENT_CERT_ID_SAN_MATCH_TYPE));
205+
}
206+
return clientCertIdSanMatchType;
207+
}
208+
209+
public String getClientCertIdSanMatchRegex() {
210+
if (clientCertIdSanMatchRegex == null) {
211+
setClientCertIdSanMatchRegex(System.getProperty(SSL_X509_CLIENT_CERT_ID_SAN_MATCH_REGEX));
212+
}
213+
return clientCertIdSanMatchRegex == null ? DEFAULT_REGEX : clientCertIdSanMatchRegex;
214+
}
215+
216+
public String getClientCertIdSanExtractRegex() {
217+
if (clientCertIdSanExtractRegex == null) {
218+
setClientCertIdSanExtractRegex(System.getProperty(SSL_X509_CLIENT_CERT_ID_SAN_EXTRACT_REGEX));
219+
}
220+
return clientCertIdSanExtractRegex == null ? DEFAULT_REGEX : clientCertIdSanExtractRegex;
221+
}
222+
223+
public int getClientCertIdSanExtractMatcherGroupIndex() {
224+
if (clientCertIdSanExtractMatcherGroupIndex == -1) {
225+
setClientCertIdSanExtractMatcherGroupIndex(
226+
System.getProperty(SSL_X509_CLIENT_CERT_ID_SAN_EXTRACT_MATCHER_GROUP_INDEX));
227+
}
228+
return clientCertIdSanExtractMatcherGroupIndex == -1 ? 0
229+
: clientCertIdSanExtractMatcherGroupIndex;
230+
}
231+
232+
/**
233+
* Getters for X509 Znode Group Acl properties
234+
*/
235+
public boolean isX509ClientIdAsAclEnabled() {
236+
return Boolean.parseBoolean(x509ClientIdAsAclEnabled) || Boolean
237+
.parseBoolean(System.getProperty(SET_X509_CLIENT_ID_AS_ACL));
238+
}
239+
240+
public String getZnodeGroupAclSuperUserId() {
241+
if (znodeGroupAclSuperUserId == null) {
242+
setZnodeGroupAclSuperUserId(System.getProperty(ZOOKEEPER_ZNODEGROUPACL_SUPERUSER_ID));
243+
}
244+
return znodeGroupAclSuperUserId;
245+
}
246+
247+
public Set<String> getZnodeGroupAclCrossDomainAccessDomains() {
248+
if (crossDomainAccessDomains == null) {
249+
synchronized (crossDomainAccessDomainsLock) {
250+
if (crossDomainAccessDomains == null) {
251+
crossDomainAccessDomains = loadCrossDomainAccessDomainNames();
252+
}
253+
}
254+
}
255+
return crossDomainAccessDomains;
256+
}
257+
258+
public Set<String> getZnodeGroupAclOpenReadAccessPathPrefixes() {
259+
if (openReadAccessPathPrefixes == null) {
260+
synchronized (openReadAccessPathPrefixesLock) {
261+
if (openReadAccessPathPrefixes == null) {
262+
openReadAccessPathPrefixes = loadOpenReadAccessPathPrefixes();
263+
}
264+
}
265+
}
266+
return openReadAccessPathPrefixes;
267+
}
268+
269+
public String getZnodeGroupAclServerDedicatedDomain() {
270+
if (znodeGroupAclServerDedicatedDomain == null) {
271+
setZnodeGroupAclServerDedicatedDomain(System.getProperty(DEDICATED_DOMAIN));
272+
}
273+
return znodeGroupAclServerDedicatedDomain;
274+
}
275+
276+
public String getZnodeGroupAclClientUriDomainMappingRootPath() {
277+
if (znodeGroupAclClientUriDomainMappingRootPath == null) {
278+
setZnodeGroupAclClientUriDomainMappingRootPath(
279+
System.getProperty(CLIENT_URI_DOMAIN_MAPPING_ROOT_PATH));
280+
}
281+
return znodeGroupAclClientUriDomainMappingRootPath;
282+
}
283+
284+
/**
285+
* Helper method for Znode Group Acl feature
286+
* Get open read access path prefixes from config
287+
* @return A set of path prefixes
288+
*/
289+
private Set<String> loadOpenReadAccessPathPrefixes() {
290+
if (znodeGroupAclOpenReadAccessPathPrefixStr == null) {
291+
setZnodeGroupAclOpenReadAccessPathPrefixStr(System.getProperty(OPEN_READ_ACCESS_PATH_PREFIX));
292+
}
293+
if (znodeGroupAclOpenReadAccessPathPrefixStr == null
294+
|| znodeGroupAclOpenReadAccessPathPrefixStr.isEmpty()) {
295+
return Collections.emptySet();
296+
}
297+
return Arrays.stream(znodeGroupAclOpenReadAccessPathPrefixStr.split(","))
298+
.filter(str -> str.length() > 0).collect(Collectors.toSet());
299+
}
300+
301+
/**
302+
* Helper method for Znode Group Acl feature
303+
* Get the domain names that are mapped to cross-domain access privilege
304+
* @return A set of domain names
305+
*/
306+
private Set<String> loadCrossDomainAccessDomainNames() {
307+
if (znodeGroupAclCrossDomainAccessDomainNameStr == null) {
308+
setZnodeGroupAclCrossDomainAccessDomainNameStr(System.getProperty(CROSS_DOMAIN_ACCESS_DOMAIN_NAME));
309+
}
310+
if (znodeGroupAclCrossDomainAccessDomainNameStr == null || znodeGroupAclCrossDomainAccessDomainNameStr
311+
.isEmpty()) {
312+
return Collections.emptySet();
313+
}
314+
return Arrays.stream(znodeGroupAclCrossDomainAccessDomainNameStr.split(","))
315+
.filter(str -> str.length() > 0).collect(Collectors.toSet());
316+
}
317+
318+
@VisibleForTesting
319+
public static void reset() {
320+
synchronized (X509AuthenticationConfig.class) {
321+
instance = null;
322+
}
323+
}
324+
}

0 commit comments

Comments
 (0)