This is an automated email from the ASF dual-hosted git repository. amagyar pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push: new 82cd96d99 KNOX-2777 - Add configurations for concurrent session verifier feature (#608) 82cd96d99 is described below commit 82cd96d99398cc9aad86596fb65240ccd0244498 Author: MrtnBalazs <77810082+mrtnbal...@users.noreply.github.com> AuthorDate: Fri Jul 22 13:24:34 2022 +0200 KNOX-2777 - Add configurations for concurrent session verifier feature (#608) Co-authored-by: MrtnBalazs <mbal...@cloudera.com> --- .../gateway/config/impl/GatewayConfigImpl.java | 31 ++++ .../gateway/config/impl/GatewayConfigImplTest.java | 47 ++++++ .../org/apache/knox/gateway/GatewayTestConfig.java | 20 +++ .../session/control/ConcurrentSessionVerifier.java | 106 ++++++++++++++ .../control/ConcurrentSessionVerifierTest.java | 161 +++++++++++++++++++++ .../apache/knox/gateway/config/GatewayConfig.java | 8 + 6 files changed, 373 insertions(+) diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java index cf0d42ba8..3e45bac36 100644 --- a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java +++ b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java @@ -293,6 +293,16 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig { private static final String GATEWAY_DATABASE_VERIFY_SERVER_CERT = GATEWAY_CONFIG_FILE_PREFIX + ".database.ssl.verify.server.cert"; private static final String GATEWAY_DATABASE_TRUSTSTORE_FILE = GATEWAY_CONFIG_FILE_PREFIX + ".database.ssl.truststore.file"; + // Concurrent session properties + private static final String PRIVILEGED_USERS = "privileged.users"; + private static final String NON_PRIVILEGED_USERS = "non." + PRIVILEGED_USERS; + private static final String GATEWAY_PRIVILEGED_USERS_CONCURRENT_SESSION_LIMIT = GATEWAY_CONFIG_FILE_PREFIX + "." + PRIVILEGED_USERS + ".concurrent.session.limit"; + private static final String GATEWAY_NON_PRIVILEGED_USERS_CONCURRENT_SESSION_LIMIT = GATEWAY_CONFIG_FILE_PREFIX + "." + NON_PRIVILEGED_USERS + ".concurrent.session.limit"; + private static final int GATEWAY_PRIVILEGED_USERS_CONCURRENT_SESSION_LIMIT_DEFAULT = 3; + private static final int GATEWAY_NON_PRIVILEGED_USERS_CONCURRENT_SESSION_LIMIT_DEFAULT = 2; + private static final String GATEWAY_PRIVILEGED_USERS = GATEWAY_CONFIG_FILE_PREFIX + "." + PRIVILEGED_USERS; + private static final String GATEWAY_NON_PRIVILEGED_USERS = GATEWAY_CONFIG_FILE_PREFIX + "." + NON_PRIVILEGED_USERS; + public GatewayConfigImpl() { init(); } @@ -1335,4 +1345,25 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig { return getInt(JETTY_MAX_FORM_KEYS, ContextHandler.DEFAULT_MAX_FORM_KEYS); } + @Override + public int getPrivilegedUsersConcurrentSessionLimit() { + return getInt(GATEWAY_PRIVILEGED_USERS_CONCURRENT_SESSION_LIMIT, GATEWAY_PRIVILEGED_USERS_CONCURRENT_SESSION_LIMIT_DEFAULT); + } + + @Override + public int getNonPrivilegedUsersConcurrentSessionLimit() { + return getInt(GATEWAY_NON_PRIVILEGED_USERS_CONCURRENT_SESSION_LIMIT, GATEWAY_NON_PRIVILEGED_USERS_CONCURRENT_SESSION_LIMIT_DEFAULT); + } + + @Override + public Set<String> getPrivilegedUsers() { + final Collection<String> privilegedUsers = getTrimmedStringCollection(GATEWAY_PRIVILEGED_USERS); + return privilegedUsers == null ? Collections.emptySet() : new HashSet<>(privilegedUsers); + } + + @Override + public Set<String> getNonPrivilegedUsers() { + final Collection<String> nonPrivilegedUsers = getTrimmedStringCollection(GATEWAY_NON_PRIVILEGED_USERS); + return nonPrivilegedUsers == null ? Collections.emptySet() : new HashSet<>(nonPrivilegedUsers); + } } diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/config/impl/GatewayConfigImplTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/config/impl/GatewayConfigImplTest.java index a9c3be6ad..5ec699b53 100644 --- a/gateway-server/src/test/java/org/apache/knox/gateway/config/impl/GatewayConfigImplTest.java +++ b/gateway-server/src/test/java/org/apache/knox/gateway/config/impl/GatewayConfigImplTest.java @@ -24,6 +24,8 @@ import org.junit.Test; import java.nio.file.Paths; import java.security.KeyStore; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -424,4 +426,49 @@ public class GatewayConfigImplTest { assertEquals("myTokenStateService", gatewayConfig.getServiceParameter("tokenstate", "impl")); } + @Test + public void testDefaultConcurrentSessionLimitParameters() { + GatewayConfigImpl config = new GatewayConfigImpl(); + + assertThat(config.getPrivilegedUsersConcurrentSessionLimit(), is(3)); + assertThat(config.getNonPrivilegedUsersConcurrentSessionLimit(), is(2)); + assertThat(config.getPrivilegedUsers(), is(new HashSet<>())); + assertThat(config.getNonPrivilegedUsers(), is(new HashSet<>())); + } + + @Test + public void testNormalConcurrentSessionLimitParameters() { + GatewayConfigImpl config = new GatewayConfigImpl(); + + config.set("gateway.privileged.users.concurrent.session.limit", "5"); + assertThat(config.getPrivilegedUsersConcurrentSessionLimit(), is(5)); + config.set("gateway.non.privileged.users.concurrent.session.limit", "6"); + assertThat(config.getNonPrivilegedUsersConcurrentSessionLimit(), is(6)); + config.set("gateway.privileged.users", "admin,jeff"); + assertThat(config.getPrivilegedUsers(), is(new HashSet<>(Arrays.asList("admin", "jeff")))); + config.set("gateway.non.privileged.users", "tom,sam"); + assertThat(config.getNonPrivilegedUsers(), is(new HashSet<>(Arrays.asList("tom", "sam")))); + } + + @Test + public void testAbnormalConcurrentSessionLimitParameters() { + GatewayConfigImpl config = new GatewayConfigImpl(); + + config.set("gateway.privileged.users", ""); + assertThat(config.getPrivilegedUsers(), is(new HashSet<>())); + config.set("gateway.non.privileged.users", ""); + config.set("gateway.privileged.users", " "); + assertThat(config.getPrivilegedUsers(), is(new HashSet<>())); + config.set("gateway.non.privileged.users", " "); + assertThat(config.getNonPrivilegedUsers(), is(new HashSet<>())); + + config.set("gateway.privileged.users", " admin , jeff "); + assertThat(config.getPrivilegedUsers(), is(new HashSet<>(Arrays.asList("admin", "jeff")))); + config.set("gateway.non.privileged.users", " tom , sam "); + assertThat(config.getNonPrivilegedUsers(), is(new HashSet<>(Arrays.asList("tom", "sam")))); + config.set("gateway.privileged.users", " guest "); + assertThat(config.getPrivilegedUsers(), is(new HashSet<>(Arrays.asList("guest")))); + config.set("gateway.non.privileged.users", " guest "); + assertThat(config.getNonPrivilegedUsers(), is(new HashSet<>(Arrays.asList("guest")))); + } } diff --git a/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java b/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java index ec7367fce..24d07b430 100644 --- a/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java +++ b/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java @@ -940,4 +940,24 @@ public class GatewayTestConfig extends Configuration implements GatewayConfig { public int getJettyMaxFormKeys() { return 0; } + + @Override + public int getPrivilegedUsersConcurrentSessionLimit() { + return 0; + } + + @Override + public int getNonPrivilegedUsersConcurrentSessionLimit() { + return 0; + } + + @Override + public Set<String> getPrivilegedUsers() { + return null; + } + + @Override + public Set<String> getNonPrivilegedUsers() { + return null; + } } diff --git a/gateway-spi-common/src/main/java/org/apache/knox/gateway/session/control/ConcurrentSessionVerifier.java b/gateway-spi-common/src/main/java/org/apache/knox/gateway/session/control/ConcurrentSessionVerifier.java new file mode 100644 index 000000000..e06633ff6 --- /dev/null +++ b/gateway-spi-common/src/main/java/org/apache/knox/gateway/session/control/ConcurrentSessionVerifier.java @@ -0,0 +1,106 @@ +/* + * 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.knox.gateway.session.control; + + +import org.apache.knox.gateway.config.GatewayConfig; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class ConcurrentSessionVerifier { + public static final ConcurrentSessionVerifier INSTANCE = new ConcurrentSessionVerifier(); + private Set<String> privilegedUsers; + private Set<String> nonPrivilegedUsers; + private int privilegedUserConcurrentSessionLimit; + private int nonPrivilegedUserConcurrentSessionLimit; + private Map<String, Integer> concurrentSessionCounter; + private final Lock sessionCountModifyLock = new ReentrantLock(); + + private ConcurrentSessionVerifier() { + } + + public static ConcurrentSessionVerifier getInstance() { + return INSTANCE; + } + + public void init(GatewayConfig config) { + this.privilegedUsers = config.getPrivilegedUsers(); + this.nonPrivilegedUsers = config.getNonPrivilegedUsers(); + this.privilegedUserConcurrentSessionLimit = config.getPrivilegedUsersConcurrentSessionLimit(); + this.nonPrivilegedUserConcurrentSessionLimit = config.getNonPrivilegedUsersConcurrentSessionLimit(); + this.concurrentSessionCounter = new ConcurrentHashMap<>(); + } + + public boolean verifySessionForUser(String username) { + if (!privilegedUsers.contains(username) && !nonPrivilegedUsers.contains(username)) { + return true; + } + + sessionCountModifyLock.lock(); + try { + concurrentSessionCounter.putIfAbsent(username, 0); + if (privilegedUserCheckLimitReached(username) || nonPrivilegedUserCheckLimitReached(username)) { + return false; + } + concurrentSessionCounter.compute(username, (key, value) -> value + 1); + } finally { + sessionCountModifyLock.unlock(); + } + return true; + } + + private boolean privilegedUserCheckLimitReached(String username) { + if (privilegedUserConcurrentSessionLimit < 0) { + return false; + } + return privilegedUsers.contains(username) && (concurrentSessionCounter.get(username) >= privilegedUserConcurrentSessionLimit); + } + + private boolean nonPrivilegedUserCheckLimitReached(String username) { + if (nonPrivilegedUserConcurrentSessionLimit < 0) { + return false; + } + return nonPrivilegedUsers.contains(username) && (concurrentSessionCounter.get(username) >= nonPrivilegedUserConcurrentSessionLimit); + } + + public void sessionEndedForUser(String username) { + sessionCountModifyLock.lock(); + try { + concurrentSessionCounter.computeIfPresent(username, (key, counter) -> decreaseCounter(counter)); + } finally { + sessionCountModifyLock.unlock(); + } + } + + private Integer decreaseCounter(Integer counter) { + counter--; + if (counter < 1) { + return null; + } else { + return counter; + } + } + + Integer getUserConcurrentSessionCount(String username) { + return concurrentSessionCounter.get(username); + } +} diff --git a/gateway-spi-common/src/test/java/org/apache/knox/gateway/session/control/ConcurrentSessionVerifierTest.java b/gateway-spi-common/src/test/java/org/apache/knox/gateway/session/control/ConcurrentSessionVerifierTest.java new file mode 100644 index 000000000..d70eca356 --- /dev/null +++ b/gateway-spi-common/src/test/java/org/apache/knox/gateway/session/control/ConcurrentSessionVerifierTest.java @@ -0,0 +1,161 @@ +/* + * 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.knox.gateway.session.control; + +import org.apache.knox.gateway.config.GatewayConfig; +import org.easymock.EasyMock; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class ConcurrentSessionVerifierTest { + + private ConcurrentSessionVerifier verifier; + + @Before + public void setUp() { + verifier = ConcurrentSessionVerifier.getInstance(); + } + + private GatewayConfig mockConfig(Set<String> privilegedUsers, Set<String> nonPrivilegedUsers, int privilegedUsersLimit, int nonPrivilegedUsersLimit) { + GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class); + EasyMock.expect(config.getPrivilegedUsers()).andReturn(privilegedUsers); + EasyMock.expect(config.getNonPrivilegedUsers()).andReturn(nonPrivilegedUsers); + EasyMock.expect(config.getPrivilegedUsersConcurrentSessionLimit()).andReturn(privilegedUsersLimit); + EasyMock.expect(config.getNonPrivilegedUsersConcurrentSessionLimit()).andReturn(nonPrivilegedUsersLimit); + EasyMock.replay(config); + return config; + } + + + @Test + public void userIsInNeitherOfTheGroups() { + GatewayConfig config = mockConfig(new HashSet<>(Arrays.asList("admin")), new HashSet<>(Arrays.asList("tom", "guest")), 3, 2); + verifier.init(config); + for (int i = 0; i < 4; i++) { + Assert.assertTrue(verifier.verifySessionForUser("sam")); + } + } + + @Test + public void userIsInBothOfTheGroups() { + GatewayConfig config = mockConfig(new HashSet<>(Arrays.asList("admin", "tom")), new HashSet<>(Arrays.asList("tom", "guest")), 3, 2); + verifier.init(config); + + Assert.assertTrue(verifier.verifySessionForUser("tom")); + Assert.assertTrue(verifier.verifySessionForUser("tom")); + Assert.assertFalse(verifier.verifySessionForUser("tom")); + + config = mockConfig(new HashSet<>(Arrays.asList("admin", "tom")), new HashSet<>(Arrays.asList("tom", "guest")), 3, 4); + verifier.init(config); + + Assert.assertTrue(verifier.verifySessionForUser("tom")); + Assert.assertTrue(verifier.verifySessionForUser("tom")); + Assert.assertTrue(verifier.verifySessionForUser("tom")); + Assert.assertFalse(verifier.verifySessionForUser("tom")); + } + + @Test + public void userIsPrivileged() { + GatewayConfig config = mockConfig(new HashSet<>(Arrays.asList("admin")), new HashSet<>(Arrays.asList("tom", "guest")), 3, 2); + verifier.init(config); + + Assert.assertTrue(verifier.verifySessionForUser("admin")); + Assert.assertTrue(verifier.verifySessionForUser("admin")); + Assert.assertTrue(verifier.verifySessionForUser("admin")); + Assert.assertFalse(verifier.verifySessionForUser("admin")); + Assert.assertFalse(verifier.verifySessionForUser("admin")); + verifier.sessionEndedForUser("admin"); + Assert.assertTrue(verifier.verifySessionForUser("admin")); + Assert.assertFalse(verifier.verifySessionForUser("admin")); + Assert.assertFalse(verifier.verifySessionForUser("admin")); + } + + @Test + public void userIsNotPrivileged() { + GatewayConfig config = mockConfig(new HashSet<>(Arrays.asList("admin")), new HashSet<>(Arrays.asList("tom", "guest")), 3, 2); + verifier.init(config); + + Assert.assertTrue(verifier.verifySessionForUser("tom")); + Assert.assertTrue(verifier.verifySessionForUser("tom")); + Assert.assertFalse(verifier.verifySessionForUser("tom")); + Assert.assertFalse(verifier.verifySessionForUser("tom")); + verifier.sessionEndedForUser("tom"); + Assert.assertTrue(verifier.verifySessionForUser("tom")); + Assert.assertFalse(verifier.verifySessionForUser("tom")); + Assert.assertFalse(verifier.verifySessionForUser("tom")); + } + + @Test + public void privilegedLimitIsZero() { + GatewayConfig config = mockConfig(new HashSet<>(Arrays.asList("admin")), new HashSet<>(Arrays.asList("tom", "guest")), 0, 2); + verifier.init(config); + + Assert.assertFalse(verifier.verifySessionForUser("admin")); + } + + @Test + public void nonPrivilegedLimitIsZero() { + GatewayConfig config = mockConfig(new HashSet<>(Arrays.asList("admin")), new HashSet<>(Arrays.asList("tom", "guest")), 3, 0); + verifier.init(config); + + Assert.assertFalse(verifier.verifySessionForUser("tom")); + } + + @Test + public void sessionsDoNotGoToNegative() { + GatewayConfig config = mockConfig(new HashSet<>(Arrays.asList("admin")), new HashSet<>(Arrays.asList("tom", "guest")), 2, 2); + verifier.init(config); + + Assert.assertNull(verifier.getUserConcurrentSessionCount("admin")); + verifier.verifySessionForUser("admin"); + Assert.assertEquals(1, verifier.getUserConcurrentSessionCount("admin").intValue()); + verifier.sessionEndedForUser("admin"); + Assert.assertNull(verifier.getUserConcurrentSessionCount("admin")); + verifier.sessionEndedForUser("admin"); + Assert.assertNull(verifier.getUserConcurrentSessionCount("admin")); + verifier.verifySessionForUser("admin"); + Assert.assertEquals(1, verifier.getUserConcurrentSessionCount("admin").intValue()); + + Assert.assertNull(verifier.getUserConcurrentSessionCount("tom")); + verifier.verifySessionForUser("tom"); + Assert.assertEquals(1, verifier.getUserConcurrentSessionCount("tom").intValue()); + verifier.sessionEndedForUser("tom"); + Assert.assertNull(verifier.getUserConcurrentSessionCount("tom")); + verifier.sessionEndedForUser("tom"); + Assert.assertNull(verifier.getUserConcurrentSessionCount("tom")); + verifier.verifySessionForUser("tom"); + Assert.assertEquals(1, verifier.getUserConcurrentSessionCount("tom").intValue()); + } + + @Test + public void negativeLimitMeansUnlimited() { + GatewayConfig config = mockConfig(new HashSet<>(Arrays.asList("admin")), new HashSet<>(Arrays.asList("tom", "guest")), -2, -2); + verifier.init(config); + + for (int i = 0; i < 10; i++) { + Assert.assertTrue(verifier.verifySessionForUser("admin")); + Assert.assertTrue(verifier.verifySessionForUser("tom")); + } + } +} + diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java index faa5ea32a..bf6eee3b1 100644 --- a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java +++ b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java @@ -813,4 +813,12 @@ public interface GatewayConfig { int getJettyMaxFormContentSize(); int getJettyMaxFormKeys(); + + int getPrivilegedUsersConcurrentSessionLimit(); + + int getNonPrivilegedUsersConcurrentSessionLimit(); + + Set<String> getPrivilegedUsers(); + + Set<String> getNonPrivilegedUsers(); }