Skip to content

Commit

Permalink
GEODE-3974: function security improvement (#1287)
Browse files Browse the repository at this point in the history
* GEODE-3974: function security improvement

* function executed on a local member does not log out user accidentally
* Mark some internal functions as InternalEntity
* test refactor
  • Loading branch information
jinmeiliao authored Jan 19, 2018
1 parent 081aa75 commit 00be4f9
Show file tree
Hide file tree
Showing 22 changed files with 315 additions and 753 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionAttributes;
import org.apache.geode.cache.Scope;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.execute.Function;
import org.apache.geode.cache.execute.FunctionContext;
import org.apache.geode.cache.partition.PartitionRegionHelper;
Expand Down Expand Up @@ -64,19 +63,10 @@ public class CreateRegionFunction implements Function, Declarable, DataSerializa
"__regionConfigurationMetadata";

public CreateRegionFunction() {
this(CacheFactory.getAnyInstance());
}

public CreateRegionFunction(Cache cache) {
this.cache = cache;
this.cache = CacheFactory.getAnyInstance();
this.regionConfigurationsRegion = createRegionConfigurationMetadataRegion();
}

public CreateRegionFunction(ClientCache cache) {
this.cache = null;
this.regionConfigurationsRegion = null;
}

public void execute(FunctionContext context) {
RegionConfiguration configuration = (RegionConfiguration) context.getArguments();
if (this.cache.getLogger().fineEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

import org.apache.geode.DataSerializable;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.Declarable;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.execute.Function;
Expand All @@ -42,31 +41,22 @@ public class TouchPartitionedRegionEntriesFunction

private static final long serialVersionUID = -3700389655056961153L;

private final Cache cache;

public static final String ID = "touch-partitioned-region-entries";

public TouchPartitionedRegionEntriesFunction() {
this(CacheFactory.getAnyInstance());
}

public TouchPartitionedRegionEntriesFunction(Cache cache) {
this.cache = cache;
}

@SuppressWarnings("unchecked")
public void execute(FunctionContext context) {
RegionFunctionContext rfc = (RegionFunctionContext) context;
Set<String> keys = (Set<String>) rfc.getFilter();

Cache cache = context.getCache();
// Get local (primary) data for the context
Region primaryDataSet = PartitionRegionHelper.getLocalDataForContext(rfc);

if (this.cache.getLogger().fineEnabled()) {
if (cache.getLogger().fineEnabled()) {
StringBuilder builder = new StringBuilder();
builder.append("Function ").append(ID).append(" received request to touch ")
.append(primaryDataSet.getFullPath()).append("->").append(keys);
this.cache.getLogger().fine(builder.toString());
cache.getLogger().fine(builder.toString());
}

// Retrieve each value to update the lastAccessedTime.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@

import org.apache.geode.DataSerializable;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.Declarable;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.execute.Function;
import org.apache.geode.cache.execute.FunctionContext;
import org.apache.geode.management.internal.security.ResourcePermissions;
import org.apache.geode.security.ResourcePermission;

/**
Expand All @@ -41,31 +41,22 @@ public class TouchReplicatedRegionEntriesFunction

private static final long serialVersionUID = -7424895036162243564L;

private final Cache cache;

public static final String ID = "touch-replicated-region-entries";

public TouchReplicatedRegionEntriesFunction() {
this(CacheFactory.getAnyInstance());
}

public TouchReplicatedRegionEntriesFunction(Cache cache) {
this.cache = cache;
}

public void execute(FunctionContext context) {
Object[] arguments = (Object[]) context.getArguments();
Cache cache = context.getCache();
String regionName = (String) arguments[0];
Set<String> keys = (Set<String>) arguments[1];
if (this.cache.getLogger().fineEnabled()) {
if (cache.getLogger().fineEnabled()) {
StringBuilder builder = new StringBuilder();
builder.append("Function ").append(ID).append(" received request to touch ")
.append(regionName).append("->").append(keys);
this.cache.getLogger().fine(builder.toString());
cache.getLogger().fine(builder.toString());
}

// Retrieve the appropriate Region and value to update the lastAccessedTime
Region region = this.cache.getRegion(regionName);
Region region = cache.getRegion(regionName);
if (region != null) {
region.getAll(keys);
}
Expand All @@ -75,9 +66,10 @@ public void execute(FunctionContext context) {
}

@Override
// the actual regionName used in the function body is passed in as an function arugment,
// this regionName is not really used in function. Hence requiring DATA:READ on all regions
public Collection<ResourcePermission> getRequiredPermissions(String regionName) {
return Collections.singletonList(new ResourcePermission(ResourcePermission.Resource.DATA,
ResourcePermission.Operation.READ, regionName));
return Collections.singletonList(ResourcePermissions.DATA_READ);
}

public String getId() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@

package org.apache.geode.modules.util;

import java.util.HashMap;
import java.util.Map;

import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;

import org.apache.geode.cache.RegionShortcut;
import org.apache.geode.cache.execute.Function;
import org.apache.geode.cache.execute.FunctionService;
import org.apache.geode.examples.SimpleSecurityManager;
import org.apache.geode.test.junit.categories.IntegrationTest;
Expand All @@ -38,66 +42,34 @@ public class ModuleFunctionsSecurityTest {
@ClassRule
public static ServerStarterRule server =
new ServerStarterRule().withJMXManager().withSecurityManager(SimpleSecurityManager.class)
.withRegion(RegionShortcut.REPLICATE, "REPLICATE_1")
.withRegion(RegionShortcut.PARTITION, "PARTITION_1").withAutoStart();
.withRegion(RegionShortcut.REPLICATE, "AuthRegion").withAutoStart();

@Rule
public GfshCommandRule gfsh =
new GfshCommandRule(server::getJmxPort, GfshCommandRule.PortType.jmxManager);

private static Map<Function, String> functionStringMap = new HashMap<>();

@BeforeClass
public static void setupClass() {
FunctionService.registerFunction(new BootstrappingFunction());
FunctionService.registerFunction(new CreateRegionFunction());
FunctionService.registerFunction(new RegionSizeFunction());
FunctionService.registerFunction(new TouchPartitionedRegionEntriesFunction());
FunctionService.registerFunction(new TouchReplicatedRegionEntriesFunction());
}
functionStringMap.put(new BootstrappingFunction(), "CLUSTER:MANAGE");
functionStringMap.put(new CreateRegionFunction(), "DATA:MANAGE");
functionStringMap.put(new RegionSizeFunction(), "DATA:READ:AuthRegion");
functionStringMap.put(new TouchPartitionedRegionEntriesFunction(), "DATA:READ:AuthRegion");
functionStringMap.put(new TouchReplicatedRegionEntriesFunction(), "DATA:READ");

@Test
@ConnectionConfiguration(user = "dataWrite", password = "dataWrite")
public void testInvalidPermissionsForBootstrappingFunction() throws Exception {
gfsh.executeAndAssertThat("execute function --id=" + BootstrappingFunction.ID)
.tableHasColumnWithExactValuesInAnyOrder(RESULT_HEADER,
"Exception: dataWrite not authorized for CLUSTER:MANAGE")
.statusIsError();
}

@Test
@ConnectionConfiguration(user = "dataWrite", password = "dataWrite")
public void testInvalidPermissionsForCreateRegionFunction() throws Exception {
gfsh.executeAndAssertThat("execute function --id=" + CreateRegionFunction.ID)
.tableHasColumnWithExactValuesInAnyOrder(RESULT_HEADER,
"Exception: dataWrite not authorized for DATA:MANAGE")
.statusIsError();
}

@Test
@ConnectionConfiguration(user = "dataWrite", password = "dataWrite")
public void testInvalidPermissionsForRegionSizeFunction() throws Exception {
gfsh.executeAndAssertThat("execute function --region=REPLICATE_1 --id=" + RegionSizeFunction.ID)
.tableHasColumnWithExactValuesInAnyOrder(RESULT_HEADER,
"Exception: dataWrite not authorized for DATA:READ:REPLICATE_1")
.statusIsError();
}

@Test
@ConnectionConfiguration(user = "dataWrite", password = "dataWrite")
public void testInvalidPermissionsForTouchPartitionedRegionEntriesFunction() throws Exception {
gfsh.executeAndAssertThat(
"execute function --region=PARTITION_1 --id=" + TouchPartitionedRegionEntriesFunction.ID)
.tableHasColumnWithExactValuesInAnyOrder(RESULT_HEADER,
"Exception: dataWrite not authorized for DATA:READ:PARTITION_1")
.statusIsError();
functionStringMap.keySet().forEach(FunctionService::registerFunction);
}

@Test
@ConnectionConfiguration(user = "dataWrite", password = "dataWrite")
public void testInvalidPermissionsForTouchReplicatedRegionEntriesFunction() throws Exception {
gfsh.executeAndAssertThat(
"execute function --region=REPLICATE_1 --id=" + TouchReplicatedRegionEntriesFunction.ID)
.tableHasColumnWithExactValuesInAnyOrder(RESULT_HEADER,
"Exception: dataWrite not authorized for DATA:READ:REPLICATE_1")
.statusIsError();
@ConnectionConfiguration(user = "user", password = "user")
public void functionRequireExpectedPermission() throws Exception {
functionStringMap.entrySet().stream().forEach(entry -> {
Function function = entry.getKey();
String permission = entry.getValue();
gfsh.executeAndAssertThat("execute function --region=AuthRegion --id=" + function.getId())
.tableHasRowCount(RESULT_HEADER, 1)
.tableHasColumnWithValuesContaining(RESULT_HEADER, permission).statusIsError();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

package org.apache.geode.connectors.jdbc.internal.cli;

import java.util.HashMap;
import java.util.Map;

import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
Expand Down Expand Up @@ -48,20 +51,6 @@ CliFunctionResult getFunctionResult(JdbcConnectorService service,

@Category({IntegrationTest.class, SecurityException.class})
public class JDBCConnectorFunctionsSecurityTest {

private static Function alterConnectionFunction = new AlterConnectionFunction();
private static Function alterMappingFunction = new AlterMappingFunction();
private static Function createConnectionFunction = new CreateConnectionFunction();
private static Function createMappingFunction = new CreateMappingFunction();
private static Function describeConnectionFunction = new DescribeConnectionFunction();
private static Function describeMappingFunction = new DescribeMappingFunction();
private static Function destroyConnectionFunction = new DestroyConnectionFunction();
private static Function destroyMappingFunction = new DestroyMappingFunction();
private static Function listConnectionFunction = new ListConnectionFunction();
private static Function listMappingFunction = new ListMappingFunction();
private static Function inheritsDefaultPermissionsFunction =
new InheritsDefaultPermissionsJDBCFunction();

@ClassRule
public static ServerStarterRule server = new ServerStarterRule().withJMXManager()
.withSecurityManager(SimpleSecurityManager.class).withAutoStart();
Expand All @@ -70,81 +59,35 @@ public class JDBCConnectorFunctionsSecurityTest {
public GfshCommandRule gfsh =
new GfshCommandRule(server::getJmxPort, GfshCommandRule.PortType.jmxManager);

private static Map<Function, String> functionStringMap = new HashMap<>();

@BeforeClass
public static void setupClass() {
FunctionService.registerFunction(alterConnectionFunction);
FunctionService.registerFunction(alterMappingFunction);
FunctionService.registerFunction(createConnectionFunction);
FunctionService.registerFunction(createMappingFunction);
FunctionService.registerFunction(describeConnectionFunction);
FunctionService.registerFunction(describeMappingFunction);
FunctionService.registerFunction(destroyConnectionFunction);
FunctionService.registerFunction(destroyMappingFunction);
FunctionService.registerFunction(listConnectionFunction);
FunctionService.registerFunction(listMappingFunction);
FunctionService.registerFunction(inheritsDefaultPermissionsFunction);
}

@Test
@ConnectionConfiguration(user = "dataWrite", password = "dataWrite")
public void testInvalidPermissionsForAlterConnectionFunction() {
gfsh.executeAndAssertThat("execute function --id=" + alterConnectionFunction.getId())
.containsOutput("not authorized for CLUSTER:MANAGE").statusIsError();
functionStringMap.put(new AlterConnectionFunction(), "CLUSTER:MANAGE");
functionStringMap.put(new AlterMappingFunction(), "CLUSTER:MANAGE");
functionStringMap.put(new CreateConnectionFunction(), "CLUSTER:MANAGE");
functionStringMap.put(new CreateMappingFunction(), "CLUSTER:MANAGE");
functionStringMap.put(new DescribeConnectionFunction(), "CLUSTER:READ");
functionStringMap.put(new DescribeMappingFunction(), "CLUSTER:READ");
functionStringMap.put(new DestroyConnectionFunction(), "CLUSTER:MANAGE");
functionStringMap.put(new DestroyMappingFunction(), "CLUSTER:MANAGE");
functionStringMap.put(new ListConnectionFunction(), "CLUSTER:READ");
functionStringMap.put(new ListMappingFunction(), "CLUSTER:READ");
functionStringMap.put(new InheritsDefaultPermissionsJDBCFunction(), "CLUSTER:READ");
functionStringMap.keySet().forEach(FunctionService::registerFunction);
}

@Test
@ConnectionConfiguration(user = "dataWrite", password = "dataWrite")
public void testInvalidPermissionsForAlterMappingFunction() {
gfsh.executeAndAssertThat("execute function --id=" + alterMappingFunction.getId())
.containsOutput("not authorized for CLUSTER:MANAGE").statusIsError();
}

@Test
@ConnectionConfiguration(user = "dataWrite", password = "dataWrite")
public void testInvalidPermissionsForCreateConnectionFunction() {
gfsh.executeAndAssertThat("execute function --id=" + createConnectionFunction.getId())
.containsOutput("not authorized for CLUSTER:MANAGE").statusIsError();
}

@Test
@ConnectionConfiguration(user = "dataWrite", password = "dataWrite")
public void testInvalidPermissionsForCreateMappingFunction() {
gfsh.executeAndAssertThat("execute function --id=" + createMappingFunction.getId())
.containsOutput("not authorized for CLUSTER:MANAGE").statusIsError();
}

@Test
@ConnectionConfiguration(user = "dataWrite", password = "dataWrite")
public void testInvalidPermissionsForDescribeConnectionFunction() {
gfsh.executeAndAssertThat("execute function --id=" + describeConnectionFunction.getId())
.containsOutput("not authorized for CLUSTER:READ").statusIsError();
}

@Test
@ConnectionConfiguration(user = "dataWrite", password = "dataWrite")
public void testInvalidPermissionsForDescribeMappingFunction() {
gfsh.executeAndAssertThat("execute function --id=" + describeMappingFunction.getId())
.containsOutput("not authorized for CLUSTER:READ").statusIsError();
}

@Test
@ConnectionConfiguration(user = "dataWrite", password = "dataWrite")
public void testInvalidPermissionsForDestroyConnectionFunction() {
gfsh.executeAndAssertThat("execute function --id=" + destroyConnectionFunction.getId())
.containsOutput("not authorized for CLUSTER:MANAGE").statusIsError();
}

@Test
@ConnectionConfiguration(user = "dataWrite", password = "dataWrite")
public void testInvalidPermissionsForDestroyMappingFunction() {
gfsh.executeAndAssertThat("execute function --id=" + destroyMappingFunction.getId())
.containsOutput("not authorized for CLUSTER:MANAGE").statusIsError();
}

@Test
@ConnectionConfiguration(user = "dataWrite", password = "dataWrite")
public void testInvalidPermissionsForFunctionInheritingDefaultPermissions() {
gfsh.executeAndAssertThat("execute function --id=" + inheritsDefaultPermissionsFunction.getId())
.containsOutput("not authorized for CLUSTER:READ").statusIsError();
@ConnectionConfiguration(user = "user", password = "user")
public void functionRequireExpectedPermission() throws Exception {
functionStringMap.entrySet().stream().forEach(entry -> {
Function function = entry.getKey();
String permission = entry.getValue();
gfsh.executeAndAssertThat("execute function --id=" + function.getId())
.tableHasRowCount("Function Execution Result", 1)
.tableHasColumnWithValuesContaining("Function Execution Result", permission)
.statusIsError();
});
}
}
Loading

0 comments on commit 00be4f9

Please sign in to comment.