forked from apache/geode-examples
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
GEODE-2988: Add a client-side security example. (apache#17)
* GEODE-2988: Add a client-side security example.
- Loading branch information
1 parent
df1d7d2
commit 2fa1e11
Showing
9 changed files
with
483 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
142 changes: 142 additions & 0 deletions
142
clientSecurity/src/main/java/org/apache/geode/examples/clientSecurity/Example.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} |
Oops, something went wrong.