This is an automated email from the ASF dual-hosted git repository.
andor pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zookeeper.git
The following commit(s) were added to refs/heads/master by this push:
new dc90972 ZOOKEEPER-3239: Adding EnsembleAuthProvider to verify the
ensemble name
dc90972 is described below
commit dc909724700e39145c2ca71cee5864fe6c074e3e
Author: Jie Huang <[email protected]>
AuthorDate: Wed Feb 13 13:19:28 2019 +0100
ZOOKEEPER-3239: Adding EnsembleAuthProvider to verify the ensemble name
Author: Jie Huang <[email protected]>
Reviewers: [email protected], [email protected]
Closes #768 from jhuan31/ZOOKEEPER-3239 and squashes the following commits:
7e17e1b8a [Jie Huang] add doc for EnsembleAuthProvider
878bfb543 [Jie Huang] ZOOKEEPER-3239: Adding EnsembleAuthProvider to verify
the ensemble name
---
.../src/main/resources/markdown/zookeeperAdmin.md | 9 ++
.../org/apache/zookeeper/server/DumbWatcher.java | 2 +-
.../org/apache/zookeeper/server/ServerCnxn.java | 2 +-
.../org/apache/zookeeper/server/ServerMetrics.java | 17 ++-
.../apache/zookeeper/server/ZooKeeperServer.java | 2 +
.../auth/EnsembleAuthenticationProvider.java | 137 +++++++++++++++++++++
.../zookeeper/server/auth/ProviderRegistry.java | 6 +-
.../apache/zookeeper/server/MockServerCnxn.java | 2 +-
.../apache/zookeeper/test/EnsembleAuthTest.java | 121 ++++++++++++++++++
9 files changed, 292 insertions(+), 6 deletions(-)
diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
index 64e8adf..1df23e2 100644
--- a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
+++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
@@ -1044,6 +1044,15 @@ encryption/authentication/authorization performed by the
service.
Then set this property **zookeeper.ssl.authProvider=[scheme]** and that
provider
will be used for secure authentication.
+* *zookeeper.ensembleAuthName* :
+ (Java system property only: **zookeeper.ensembleAuthName**)
+ **New in 3.6.0:**
+ Specify a list of comma-separated valid names/aliases of an ensemble. A
client
+ can provide the ensemble name it intends to connect as the credential for
scheme "ensemble". The EnsembleAuthenticationProvider will check the credential
against
+ the list of names/aliases of the ensemble that receives the connection
request.
+ If the credential is not in the list, the connection request will be
refused.
+ This prevents a client accidentally connecting to a wrong ensemble.
+
<a name="Experimental+Options%2FFeatures"></a>
#### Experimental Options/Features
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/DumbWatcher.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/DumbWatcher.java
index 1f64dd0..d83763b 100644
---
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/DumbWatcher.java
+++
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/DumbWatcher.java
@@ -54,7 +54,7 @@ public class DumbWatcher extends ServerCnxn {
int getSessionTimeout() { return 0; }
@Override
- void close() { }
+ public void close() { }
@Override
public void sendResponse(ReplyHeader h, Record r, String tag, String
cacheKey, Stat stat) throws IOException { }
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxn.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxn.java
index b0088d1..3289fd4 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxn.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxn.java
@@ -103,7 +103,7 @@ public abstract class ServerCnxn implements Stats, Watcher {
}
}
- abstract void close();
+ public abstract void close();
public abstract void sendResponse(ReplyHeader h, Record r,
String tag, String cacheKey, Stat stat) throws IOException;
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerMetrics.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerMetrics.java
index 1c6fd3c..ffa1317 100644
---
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerMetrics.java
+++
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerMetrics.java
@@ -77,7 +77,22 @@ public enum ServerMetrics {
BYTES_RECEIVED_COUNT(new SimpleCounter("bytes_received_count")),
RESPONSE_PACKET_CACHE_HITS(new
SimpleCounter("response_packet_cache_hits")),
- RESPONSE_PACKET_CACHE_MISSING(new
SimpleCounter("response_packet_cache_misses"));
+ RESPONSE_PACKET_CACHE_MISSING(new
SimpleCounter("response_packet_cache_misses")),
+
+ /*
+ * Number of successful matches of expected ensemble name in
EnsembleAuthenticationProvider.
+ */
+ ENSEMBLE_AUTH_SUCCESS(new SimpleCounter("ensemble_auth_success")),
+
+ /*
+ * Number of unsuccessful matches of expected ensemble name in
EnsembleAuthenticationProvider.
+ */
+ ENSEMBLE_AUTH_FAIL(new SimpleCounter("ensemble_auth_fail")),
+
+ /*
+ * Number of client auth requests with no ensemble set in
EnsembleAuthenticationProvider.
+ */
+ ENSEMBLE_AUTH_SKIP(new SimpleCounter("ensemble_auth_skip"));
private final Metric metric;
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServer.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServer.java
index 4b9b715..b14d1e6 100644
---
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServer.java
+++
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServer.java
@@ -1203,6 +1203,8 @@ public class ZooKeeperServer implements SessionExpirer,
ServerStats.Provider {
Code authReturn = KeeperException.Code.AUTHFAILED;
if(ap != null) {
try {
+ // handleAuthentication may close the connection, to allow
the client to choose
+ // a different server to connect to.
authReturn = ap.handleAuthentication(new
ServerAuthenticationProvider.ServerObjs(this, cnxn), authPacket.getAuth());
} catch(RuntimeException e) {
LOG.warn("Caught runtime exception from
AuthenticationProvider: " + scheme + " due to " + e);
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/EnsembleAuthenticationProvider.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/EnsembleAuthenticationProvider.java
new file mode 100644
index 0000000..2a4c639
--- /dev/null
+++
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/EnsembleAuthenticationProvider.java
@@ -0,0 +1,137 @@
+/**
+ * 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.zookeeper.server.auth;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.ZooKeeper;
+import org.apache.zookeeper.data.Id;
+import org.apache.zookeeper.jmx.MBeanRegistry;
+import org.apache.zookeeper.server.ServerCnxn;
+import org.apache.zookeeper.server.ServerMetrics;
+import org.apache.zookeeper.server.ZooKeeperServer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.zookeeper.jmx.ZKMBeanInfo;
+
+import javax.management.JMException;
+
+/**
+ * This is not a true AuthenticationProvider in the strict sense. it does
+ * handle add auth requests, but rather than authenticate the client, it checks
+ * to make sure that the ensemble name the client intends to connect to
+ * matches the name that the server thinks it belongs to. if the name does not
match,
+ * this provider will close the connection.
+ */
+
+public class EnsembleAuthenticationProvider implements AuthenticationProvider {
+ private static final Logger LOG = LoggerFactory
+ .getLogger(EnsembleAuthenticationProvider.class);
+
+ public static final String ENSEMBLE_PROPERTY =
"zookeeper.ensembleAuthName";
+ private static final int MIN_LOGGING_INTERVAL_MS = 1000;
+ private Set<String> ensembleNames;
+
+ public EnsembleAuthenticationProvider() {
+ String namesCSV = System.getProperty(ENSEMBLE_PROPERTY);
+ if (namesCSV != null) {
+ LOG.info("Set expected ensemble names to {}", namesCSV);
+ setEnsembleNames(namesCSV);
+ }
+ }
+
+ public void setEnsembleNames(String namesCSV) {
+ ensembleNames = new HashSet<String>();
+ for (String name: namesCSV.split(",")) {
+ ensembleNames.add(name.trim());
+ }
+ }
+
+ /* provider methods */
+ @Override
+ public String getScheme() {
+ return "ensemble";
+ }
+
+ /**
+ * if things go bad, we don't want to freak out with the logging, so track
+ * the last time we logged something here.
+ */
+ private long lastFailureLogged;
+
+ @Override
+ public KeeperException.Code
+ handleAuthentication(ServerCnxn cnxn, byte[] authData)
+ {
+ if (authData == null || authData.length == 0) {
+ ServerMetrics.ENSEMBLE_AUTH_SKIP.add(1);
+ return KeeperException.Code.OK;
+ }
+
+ String receivedEnsembleName = new String(authData);
+
+ if (ensembleNames == null) {
+ ServerMetrics.ENSEMBLE_AUTH_SKIP.add(1);
+ return KeeperException.Code.OK;
+ }
+
+ if (ensembleNames.contains(receivedEnsembleName)) {
+ ServerMetrics.ENSEMBLE_AUTH_SUCCESS.add(1);
+ return KeeperException.Code.OK;
+ }
+
+ long currentTime = System.currentTimeMillis();
+ if (lastFailureLogged + MIN_LOGGING_INTERVAL_MS < currentTime) {
+ String id =
cnxn.getRemoteSocketAddress().getAddress().getHostAddress();
+ LOG.warn("Unexpected ensemble name: ensemble name: {} client ip:
{}", receivedEnsembleName, id);
+ lastFailureLogged = currentTime;
+ }
+ /*
+ * we are doing a close here rather than returning some other error
+ * since we want the client to choose another server to connect to. if
+ * we return an error, the client will get a fatal auth error and
+ * shutdown.
+ */
+ ServerMetrics.ENSEMBLE_AUTH_FAIL.add(1);
+ cnxn.close();
+ return KeeperException.Code.BADARGUMENTS;
+ }
+
+ /*
+ * since we aren't a true provider we return false for everything so that
+ * it isn't used in ACLs.
+ */
+ @Override
+ public boolean matches(String id, String aclExpr) {
+ return false;
+ }
+
+ @Override
+ public boolean isAuthenticated() {
+ return false;
+ }
+
+ @Override
+ public boolean isValid(String id) {
+ return false;
+ }
+}
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/ProviderRegistry.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/ProviderRegistry.java
index ed69f92..9b4d1d0 100644
---
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/ProviderRegistry.java
+++
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/ProviderRegistry.java
@@ -44,8 +44,6 @@ public class ProviderRegistry {
public static void initialize() {
synchronized (ProviderRegistry.class) {
- if (initialized)
- return;
IPAuthenticationProvider ipp = new IPAuthenticationProvider();
DigestAuthenticationProvider digp = new
DigestAuthenticationProvider();
authenticationProviders.put(ipp.getScheme(), ipp);
@@ -80,6 +78,10 @@ public class ProviderRegistry {
return authenticationProviders.get(scheme);
}
+ public static void removeProvider(String scheme) {
+ authenticationProviders.remove(scheme);
+ }
+
public static String listProviders() {
StringBuilder sb = new StringBuilder();
for(String s: authenticationProviders.keySet()) {
diff --git
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/MockServerCnxn.java
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/MockServerCnxn.java
index a8fdeaf..c31dabf 100644
---
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/MockServerCnxn.java
+++
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/MockServerCnxn.java
@@ -40,7 +40,7 @@ public class MockServerCnxn extends ServerCnxn {
}
@Override
- void close() {
+ public void close() {
}
@Override
diff --git
a/zookeeper-server/src/test/java/org/apache/zookeeper/test/EnsembleAuthTest.java
b/zookeeper-server/src/test/java/org/apache/zookeeper/test/EnsembleAuthTest.java
new file mode 100644
index 0000000..6724573
--- /dev/null
+++
b/zookeeper-server/src/test/java/org/apache/zookeeper/test/EnsembleAuthTest.java
@@ -0,0 +1,121 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.zookeeper.test;
+
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.ZooKeeper;
+import org.apache.zookeeper.server.auth.EnsembleAuthenticationProvider;
+import org.apache.zookeeper.server.auth.ProviderRegistry;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class EnsembleAuthTest extends ClientBase {
+
+ @Before
+ public void setUp() throws Exception {
+ System.setProperty("zookeeper.authProvider.1",
"org.apache.zookeeper.server.auth.EnsembleAuthenticationProvider");
+ super.setUp();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ System.clearProperty("zookeeper.authProvider.1");
+ System.clearProperty(EnsembleAuthenticationProvider.ENSEMBLE_PROPERTY);
+ ProviderRegistry.removeProvider("ensemble");
+ }
+
+
+ @Test
+ public void noAuth() throws Exception {
+ resetEnsembleAuth(null, false);
+ connectToEnsemble(null);
+ }
+
+ @Test
+ public void emptyAuth() throws Exception {
+ resetEnsembleAuth(null, true);
+ connectToEnsemble("foo");
+ }
+
+ @Test
+ public void skipAuth() throws Exception {
+ resetEnsembleAuth("woo", true);
+ connectToEnsemble(null);
+ }
+
+ @Test
+ public void passAuth() throws Exception {
+ resetEnsembleAuth("woo", true);
+ connectToEnsemble("woo");
+ }
+
+ @Test
+ public void passAuthCSV() throws Exception {
+ resetEnsembleAuth(" foo,bar, baz ", true);
+
+ connectToEnsemble("foo");
+ connectToEnsemble("bar");
+ connectToEnsemble("baz");
+ }
+
+ @Test(expected = KeeperException.ConnectionLossException.class)
+ public void failAuth() throws Exception {
+ resetEnsembleAuth("woo", true);
+ connectToEnsemble("goo");
+ }
+
+ @Test(expected = KeeperException.AuthFailedException.class)
+ public void removeEnsembleAuthProvider() throws Exception {
+ resetEnsembleAuth(null, false);
+ connectToEnsemble("goo");
+ }
+
+
+ private void connectToEnsemble(final String auth) throws IOException,
InterruptedException, KeeperException {
+ try (ZooKeeper zk = createClient()) {
+ // pass auth check
+ if (auth != null) {
+ zk.addAuthInfo("ensemble", auth.getBytes());
+ }
+ zk.getData("/", false, null);
+ }
+ }
+
+ private void resetEnsembleAuth(final String auth, final boolean useAuth)
throws Exception {
+ stopServer();
+ if (auth == null) {
+
System.clearProperty(EnsembleAuthenticationProvider.ENSEMBLE_PROPERTY);
+ } else {
+
System.setProperty(EnsembleAuthenticationProvider.ENSEMBLE_PROPERTY, auth);
+ }
+ if (useAuth) {
+ System.setProperty("zookeeper.authProvider.1",
+
"org.apache.zookeeper.server.auth.EnsembleAuthenticationProvider");
+ } else {
+ System.clearProperty("zookeeper.authProvider.1");
+ }
+ ProviderRegistry.removeProvider("ensemble");
+ ProviderRegistry.initialize();
+ startServer();
+ }
+}