You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by js...@apache.org on 2017/10/02 16:20:55 UTC
[geode-examples] branch develop updated: GEODE-2988: Add a
client-side security example. (#17)
This is an automated email from the ASF dual-hosted git repository.
jstewart pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode-examples.git
The following commit(s) were added to refs/heads/develop by this push:
new 2fa1e11 GEODE-2988: Add a client-side security example. (#17)
2fa1e11 is described below
commit 2fa1e1113253bc76c19552f9a4d49255069ca024
Author: Patrick Rhomberg <Pu...@users.noreply.github.com>
AuthorDate: Mon Oct 2 09:20:53 2017 -0700
GEODE-2988: Add a client-side security example. (#17)
* GEODE-2988: Add a client-side security example.
---
clientSecurity/README.md | 119 +++++++++++++++++
clientSecurity/example_security.properties | 15 +++
clientSecurity/scripts/start.gfsh | 46 +++++++
clientSecurity/scripts/stop.gfsh | 18 +++
.../geode/examples/clientSecurity/Example.java | 142 +++++++++++++++++++++
.../examples/clientSecurity/ExampleAuthInit.java | 69 ++++++++++
.../src/main/resources/example_security.json | 72 +++++++++++
gradle/rat.gradle | 1 +
settings.gradle | 1 +
9 files changed, 483 insertions(+)
diff --git a/clientSecurity/README.md b/clientSecurity/README.md
new file mode 100644
index 0000000..db806c1
--- /dev/null
+++ b/clientSecurity/README.md
@@ -0,0 +1,119 @@
+<!--
+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.
+-->
+
+# Geode security example - Client
+
+This example demonstrates basic command security and user authentication in a client application
+backed by a secured Geode cluster. This example assumes that Java and Geode are installed.
+
+## Security Basics
+
+Geode security is based on Apache Shiro.
+Permissions are defined by
+
+1. the resource accessed (`DATA` and `CLUSTER`)
+2. the operation performed (`READ`, `WRITE`, or `MANAGE`)
+3. the target for this resource operation (typically a region name)
+4. the key for the target (e.g., the key of the region's key-value pair)
+
+A single permission is represented by a `:`-separated string, e.g., `DATA:READ:region1:myKey`.
+
+Permissions need not be fully specified.
+Abridged permissions are hierarchical.
+A permission of `CLUSTER` implies `CLUSTER:READ`, `CLUSTER:WRITE`, and `CLUSTER:MANAGE`,
+ for all target regions and all key values.
+Using wildcard annotation, a permission of `CLUSTER` is equivalent to `CLUSTER:*:*:*`.
+
+
+In this example, four users with varying permissions attempt to read and write data
+ in two regions.
+* The `superUser` user has full permissions and may read and write to all regions.
+* The `dataReader` user has `DATA:READ` permission, granting read access to all regions.
+* The `dataWriter` user has `DATA:WRITE` permission, granting write access to all regions.
+* The `region1dataAdmin` has permissions `DATA:READ:region1` and `DATA:WRITE:region1`,
+ granting read and write access only to `/region1`.
+
+For more information on what permission is required for a given operation,
+ refer to the documentation.
+
+## Required Implementations
+
+ Two interfaces must be implemented to secure a Geode cluster: `AuthInitialize`
+ and `SecurityManager`.
+
+ Your implementation of `org.apache.geode.security.AuthInitialize` should handle the interaction
+ with any existing security infrastructure (e.g., ldap). In this example, we provide a trivial
+ implementation in `org.apache.geode.examples.clientSecurity.ExampleAuthInit`.
+
+ These credentials are then given to your implementation
+ of `org.apache.geode.security.SecurityManager`
+ to authenticate the user (i.e., to log in).
+ The security manager also handles authorization of the authenticated user
+ for particular operations.
+ How permissions are assigned to users is also determined by the security manager.
+ In this example,
+ we group permissions by *role*, and assign each user one or more roles in a JSON file.
+ This file is located at `src/main/resources/example_security.json`.
+
+## Demonstration of Security
+1. Set directory `geode-examples/clientSecurity` to be the current working directory.
+Each step in this example specifies paths relative to that directory.
+
+2. Build the example
+
+ $ ../gradlew build
+
+3. Start a secure cluster consisting of one locator with two servers with two regions.
+ Refer to `scripts/start.gfsh`.
+ When starting a secure cluster, you must specify a *security manager*
+ that implements authorization.
+ In this example, we use the security manager
+ `org.apache.geode.examples.clientSecurity.ExampleSecurityManager`.
+ This security manager reads a JSON file that defines which roles are granted which permissions,
+ as well as each user's username, password, and roles.
+ The JSON is present in `src/main/resources/example_security.json`.
+ You can execute the `scripts/start.gfsh` script with the command:
+
+ $ ../gradlew start
+
+4. Run the example. Each user will attempt to put data to `/region1` and `/region2`,
+ and then read data from `/region1` and `/region2`. Unauthorized reads and writes throw
+ exceptions caused by `NotAuthorizedException`, which we catch and print in this example.
+
+ $ ../gradlew run
+
+5. Stop the cluster using the script `scripts/stop.gfsh`.
+You can run this script with the command:
+
+ $ ../gradlew stop
+
+## Things to Get Right with Security
+
+- Implement `org.apache.geode.security.AuthInitialize` to pass user credentials from any existing
+ security infrastructure.
+
+- Implement `org.apache.geode.security.SecurityManager` to handle user authentication
+ and operation authorization.
+
+- Specify the `SecurityManager` by the `security-manager` property of all locator and server
+property files. An unsecured member or a member secured by a different security manager will not
+be allowed to join the cluster.
+
+- If additional properties are required by your implementation of the security manager,
+ these may be defined in your locator or server property files.
+ For instance, our implementation also requires `security-json` to be defined.
+
diff --git a/clientSecurity/example_security.properties b/clientSecurity/example_security.properties
new file mode 100644
index 0000000..d661639
--- /dev/null
+++ b/clientSecurity/example_security.properties
@@ -0,0 +1,15 @@
+# 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.
+
+security-manager = org.apache.geode.examples.security.ExampleSecurityManager
+security-json = example_security.json
diff --git a/clientSecurity/scripts/start.gfsh b/clientSecurity/scripts/start.gfsh
new file mode 100644
index 0000000..313d035
--- /dev/null
+++ b/clientSecurity/scripts/start.gfsh
@@ -0,0 +1,46 @@
+#
+# 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.
+#
+
+# Start a locator.
+# The security properties file specifies the ExampleSecurityManager to be the security manager.
+# This requires that the example_security.json be on the classpath.
+# Recall that the --classpath option is specified relative to the locator's working directory.
+
+start locator --name=locator --bind-address=127.0.0.1\
+ --security-properties-file=example_security.properties --classpath=../build/resources/main/
+
+# Now we may start our cluster.
+# Servers also require security properties to be set and a copy of the security JSON
+# We use `--server-port=0` to use a random available port.
+# Note that we can start a server with any user with CLUSTER:MANAGE permissions
+
+start server --name=server1 --locators=127.0.0.1[10334]\
+ --classpath=../build/resources/main/:../build/classes/main/\
+ --security-properties-file=./example_security.properties --server-port=0\
+ --user=superUser --password=123
+
+start server --name=server2 --locators=127.0.0.1[10334]\
+ --classpath=../build/resources/main/:../build/classes/main/\
+ --security-properties-file=./example_security.properties --server-port=0\
+ --user=superUser --password=123
+
+# To execute any online commands, we need to connect to the locator
+# To create a region, we can connect as any user with CLUSTER:MANAGE
+
+connect --user=superUser --password=123
+create region --name=region1 --type=REPLICATE
+create region --name=region2 --type=PARTITION
diff --git a/clientSecurity/scripts/stop.gfsh b/clientSecurity/scripts/stop.gfsh
new file mode 100644
index 0000000..ad068e3
--- /dev/null
+++ b/clientSecurity/scripts/stop.gfsh
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+connect --locator=127.0.0.1[10334] --user=superUser --password=123
+shutdown --include-locators=true
diff --git a/clientSecurity/src/main/java/org/apache/geode/examples/clientSecurity/Example.java b/clientSecurity/src/main/java/org/apache/geode/examples/clientSecurity/Example.java
new file mode 100644
index 0000000..c4670cd
--- /dev/null
+++ b/clientSecurity/src/main/java/org/apache/geode/examples/clientSecurity/Example.java
@@ -0,0 +1,142 @@
+/*
+ * 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.geode.examples.clientSecurity;
+
+import java.util.Properties;
+
+import org.apache.commons.lang.Validate;
+import org.apache.logging.log4j.Logger;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.client.ClientCache;
+import org.apache.geode.cache.client.ClientCacheFactory;
+import org.apache.geode.cache.client.ClientRegionShortcut;
+import org.apache.geode.internal.logging.LogService;
+
+public class Example implements AutoCloseable {
+ private static final Logger logger = LogService.getLogger();
+
+ private static final String REGION1 = "region1";
+ private static final String REGION2 = "region2";
+
+ // Some example data
+ private static final String AUTHOR_GROSSMAN = "Grossman";
+ private static final String BOOK_BY_GROSSMAN = "Soon I Will Be Invincible";
+
+ private static final String AUTHOR_ROTHFUSS = "Rothfuss";
+ private static final String BOOK_BY_ROTHFUSS = "The Name of the Wind";
+
+ private static final String AUTHOR_LYNCH = "Lynch";
+ private static final String BOOK_BY_LYNCH = "The Lies of Locke Lamora";
+
+ private static final String AUTHOR_SCALZI = "Scalzi";
+ private static final String BOOK_BY_SCALZI = "Old Man's War";
+
+ private static final String AUTHOR_SANDERSON = "Sanderson";
+ private static final String BOOK_BY_SANDERSON = "The Way of Kings";
+
+ private static final String AUTHOR_ABERCROMBIE = "Abercrombie";
+ private static final String BOOK_BY_ABERCROMBIE = "The Blade Itself";
+
+ // Each example will have its own proxy for the cache and both regions.
+ private final ClientCache cache;
+ private final Region<String, String> region1;
+ private final Region<String, String> region2;
+
+ private Example(String username) {
+ Properties props = new Properties();
+ props.setProperty("security-username", username);
+ props.setProperty("security-client-auth-init", ExampleAuthInit.class.getName());
+
+ // connect to the locator using default port 10334
+ cache = new ClientCacheFactory(props).setPoolSubscriptionEnabled(true)
+ .addPoolLocator("localhost", 10334).create();
+ region1 = cache.<String, String>createClientRegionFactory(ClientRegionShortcut.CACHING_PROXY)
+ .create(REGION1);
+ region2 = cache.<String, String>createClientRegionFactory(ClientRegionShortcut.CACHING_PROXY)
+ .create(REGION2);
+ }
+
+ public static void main(String[] args) throws Exception {
+ adminUserCanPutAndGetEverywhere();
+ writeOnlyUserCannotGet();
+ readOnlyUserCannotPut();
+ regionUserIsRestrictedByRegion();
+ }
+
+ private static void adminUserCanPutAndGetEverywhere() throws Exception {
+ String valueFromRegion;
+ try (Example example = new Example("superUser")) {
+ // All puts and gets should pass
+ example.region1.put(AUTHOR_ABERCROMBIE, BOOK_BY_ABERCROMBIE);
+ example.region2.put(AUTHOR_GROSSMAN, BOOK_BY_GROSSMAN);
+
+ valueFromRegion = example.region1.get(AUTHOR_ABERCROMBIE);
+ Validate.isTrue(BOOK_BY_ABERCROMBIE.equals(valueFromRegion));
+
+ valueFromRegion = example.region2.get(AUTHOR_GROSSMAN);
+ Validate.isTrue(BOOK_BY_GROSSMAN.equals(valueFromRegion));
+ }
+ }
+
+ private static void writeOnlyUserCannotGet() {
+ try (Example example = new Example("dataWriter")) {
+ // Writes to any region should pass
+ example.region1.put(AUTHOR_LYNCH, BOOK_BY_LYNCH);
+ example.region2.put(AUTHOR_ROTHFUSS, BOOK_BY_ROTHFUSS);
+
+ // This will fail since dataWriter does not have DATA:READ
+ example.region1.get(AUTHOR_LYNCH);
+ } catch (Exception e) {
+ logger.error("This exception should be caused by NotAuthorizedException", e);
+ }
+ }
+
+ private static void readOnlyUserCannotPut() {
+ try (Example example = new Example("dataReader")) {
+ // This will pass
+ example.region1.get(AUTHOR_LYNCH);
+ example.region2.get(AUTHOR_ROTHFUSS);
+
+ // This will fail since dataReader does not have DATA:WRITE
+ example.region1.put(AUTHOR_SANDERSON, BOOK_BY_SANDERSON);
+ } catch (Exception e) {
+ logger.error("This exception should be caused by NotAuthorizedException", e);
+ }
+ }
+
+ private static void regionUserIsRestrictedByRegion() {
+ try (Example example = new Example("region1dataAdmin")) {
+ // This user can read and write only in region1
+ example.region1.put(AUTHOR_SANDERSON, BOOK_BY_SANDERSON);
+ String valueFromRegion = example.region1.get(AUTHOR_SANDERSON);
+ Validate.isTrue(BOOK_BY_SANDERSON.equals(valueFromRegion));
+
+ // This will fail since dataReader does not have DATA:WRITE:region2
+ example.region2.put(AUTHOR_SCALZI, BOOK_BY_SCALZI);
+ } catch (Exception e) {
+ logger.error("This exception should be caused by NotAuthorizedException", e);
+ }
+ }
+
+ /**
+ * We use AutoCloseable examples to guarantee the cache closes. Failure to close the cache would
+ * cause failures when attempting to run with a new user.
+ */
+ @Override
+ public void close() throws Exception {
+ cache.close();
+ }
+}
diff --git a/clientSecurity/src/main/java/org/apache/geode/examples/clientSecurity/ExampleAuthInit.java b/clientSecurity/src/main/java/org/apache/geode/examples/clientSecurity/ExampleAuthInit.java
new file mode 100644
index 0000000..5f859f2
--- /dev/null
+++ b/clientSecurity/src/main/java/org/apache/geode/examples/clientSecurity/ExampleAuthInit.java
@@ -0,0 +1,69 @@
+/*
+ * 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.geode.examples.clientSecurity;
+
+import java.util.Properties;
+
+import org.apache.logging.log4j.Logger;
+
+import org.apache.geode.LogWriter;
+import org.apache.geode.distributed.DistributedMember;
+import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.security.AuthInitialize;
+import org.apache.geode.security.AuthenticationFailedException;
+
+public class ExampleAuthInit implements AuthInitialize {
+
+ private static final Logger logger = LogService.getLogger();
+
+ private static final String USER_NAME = "security-username";
+ private static final String PASSWORD = "security-password";
+
+ private static final String INSECURE_PASSWORD_FOR_EVERY_USER = "123";
+
+ /**
+ * The implementer would use their existing infrastructure (e.g., ldap) here to populate these
+ * properties with the user credentials. These properties will in turn be handled by the
+ * implementer's design of SecurityManager to authenticate users and authorize operations.
+ */
+ @Override
+ public Properties getCredentials(Properties securityProps) throws AuthenticationFailedException {
+ Properties credentials = new Properties();
+ String userName = securityProps.getProperty(USER_NAME);
+ if (userName == null) {
+ throw new AuthenticationFailedException(
+ "ExampleAuthInit: user name property [" + USER_NAME + "] not set.");
+ }
+ credentials.setProperty(USER_NAME, userName);
+ credentials.setProperty(PASSWORD, INSECURE_PASSWORD_FOR_EVERY_USER);
+ logger.info("SampleAuthInit: successfully obtained credentials for user " + userName);
+ return credentials;
+ }
+
+ @Override
+ public void close() {}
+
+ @Override
+ @Deprecated
+ public void init(LogWriter systemLogger, LogWriter securityLogger)
+ throws AuthenticationFailedException {}
+
+ @Override
+ @Deprecated
+ public Properties getCredentials(Properties securityProps, DistributedMember server,
+ boolean isPeer) throws AuthenticationFailedException {
+ return getCredentials(securityProps);
+ }
+}
diff --git a/clientSecurity/src/main/resources/example_security.json b/clientSecurity/src/main/resources/example_security.json
new file mode 100644
index 0000000..9d401cd
--- /dev/null
+++ b/clientSecurity/src/main/resources/example_security.json
@@ -0,0 +1,72 @@
+{
+ "roles": [
+ {
+ "name": "data",
+ "operationsAllowed": [
+ "DATA:MANAGE",
+ "DATA:WRITE",
+ "DATA:READ"
+ ]
+ },
+ {
+ "name": "cluster",
+ "operationsAllowed": [
+ "CLUSTER:MANAGE",
+ "CLUSTER:WRITE",
+ "CLUSTER:READ"
+ ]
+ },
+ {
+ "name": "region1data",
+ "operationsAllowed": [
+ "DATA:MANAGE",
+ "DATA:WRITE",
+ "DATA:READ"
+ ],
+ "regions": ["region1"]
+ },
+ {
+ "name": "dataReader",
+ "operationsAllowed": [
+ "DATA:READ"
+ ]
+ },
+ {
+ "name": "dataWriter",
+ "operationsAllowed": [
+ "DATA:WRITE"
+ ]
+ }
+ ],
+ "users": [
+ {
+ "name": "superUser",
+ "password": "123",
+ "roles": [
+ "cluster",
+ "data"
+ ]
+ },
+ {
+ "name": "region1dataAdmin",
+ "password": "123",
+ "roles": [
+ "region1data"
+ ]
+ },
+ {
+ "name": "dataReader",
+ "password": "123",
+ "roles": [
+ "dataReader"
+ ]
+ },
+ {
+ "name": "dataWriter",
+ "password": "123",
+ "roles": [
+ "dataWriter"
+ ]
+ }
+ ]
+}
diff --git a/gradle/rat.gradle b/gradle/rat.gradle
index fe73400..63e8d7b 100644
--- a/gradle/rat.gradle
+++ b/gradle/rat.gradle
@@ -59,6 +59,7 @@ rat {
'**/*.diff',
'**/*.rej',
'**/*.orig',
+ '**/*.json',
// working directories
'**/locator/**',
diff --git a/settings.gradle b/settings.gradle
index 7b579bb..240e573 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -21,3 +21,4 @@ include 'partitioned'
include 'lucene'
include 'loader'
include 'putall'
+include 'clientSecurity'
--
To stop receiving notification emails like this one, please contact
['"commits@geode.apache.org" <co...@geode.apache.org>'].