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>'].