From 0db32da20fd02b4d7cd49e28860e15625b8579cc Mon Sep 17 00:00:00 2001
From: Jinwoo Hwang <92374539+JinwooHwang@users.noreply.github.com>
Date: Wed, 26 Aug 2026 10:35:24 -0400
Subject: [PATCH 1/9] [GEODE-10619] Remediation of CVE-2026-59296: Micrometer
1.15.12 to 1.16.7 (#8044)
---
boms/geode-all-bom/src/test/resources/expected-pom.xml | 2 +-
.../geode/gradle/plugins/DependencyConstraints.groovy | 2 +-
.../src/integrationTest/resources/assembly_content.txt | 7 ++++---
.../src/integrationTest/resources/expected_jars.txt | 1 +
.../resources/gfsh_dependency_classpath.txt | 7 ++++---
.../apache/geode/metrics/internal/NoopMeterRegistry.java | 5 +----
.../src/integrationTest/resources/dependency_classpath.txt | 7 ++++---
7 files changed, 16 insertions(+), 15 deletions(-)
diff --git a/boms/geode-all-bom/src/test/resources/expected-pom.xml b/boms/geode-all-bom/src/test/resources/expected-pom.xml
index ca41c043f528..d40e19ff4966 100644
--- a/boms/geode-all-bom/src/test/resources/expected-pom.xml
+++ b/boms/geode-all-bom/src/test/resources/expected-pom.xml
@@ -195,7 +195,7 @@
io.micrometermicrometer-core
- 1.15.12
+ 1.16.7io.swagger.core.v3
diff --git a/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy b/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
index 29e0d699c211..72ae49070199 100644
--- a/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
+++ b/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
@@ -48,7 +48,7 @@ class DependencyConstraints {
deps.put("jgroups.version", "3.6.20.Final")
deps.put("log4j.version", "2.25.5")
deps.put("log4j-slf4j2-impl.version", "2.23.1")
- deps.put("micrometer.version", "1.15.12")
+ deps.put("micrometer.version", "1.16.7")
deps.put("shiro.version", "3.0.0")
// GEODE-10583: Pin Bouncy Castle (transitive via shiro-crypto-hash) to a fixed version
deps.put("bouncycastle.version", "1.85")
diff --git a/geode-assembly/src/integrationTest/resources/assembly_content.txt b/geode-assembly/src/integrationTest/resources/assembly_content.txt
index 862f8c53a3af..eb63cebdf6a6 100644
--- a/geode-assembly/src/integrationTest/resources/assembly_content.txt
+++ b/geode-assembly/src/integrationTest/resources/assembly_content.txt
@@ -1013,6 +1013,7 @@ lib/jna-5.11.0.jar
lib/jna-platform-5.11.0.jar
lib/joda-time-2.12.7.jar
lib/jopt-simple-5.0.4.jar
+lib/jspecify-1.0.1.jar
lib/jul-to-slf4j-2.0.17.jar
lib/log4j-api-2.25.5.jar
lib/log4j-core-2.25.5.jar
@@ -1024,9 +1025,9 @@ lib/lucene-analysis-phonetic-9.12.3.jar
lib/lucene-core-9.12.3.jar
lib/lucene-queries-9.12.3.jar
lib/lucene-queryparser-9.12.3.jar
-lib/micrometer-commons-1.15.12.jar
-lib/micrometer-core-1.15.12.jar
-lib/micrometer-observation-1.15.12.jar
+lib/micrometer-commons-1.16.7.jar
+lib/micrometer-core-1.16.7.jar
+lib/micrometer-observation-1.16.7.jar
lib/mx4j-3.0.2.jar
lib/mx4j-remote-3.0.2.jar
lib/mx4j-tools-3.0.1.jar
diff --git a/geode-assembly/src/integrationTest/resources/expected_jars.txt b/geode-assembly/src/integrationTest/resources/expected_jars.txt
index facbde0d7f6b..37c04b61a0b8 100644
--- a/geode-assembly/src/integrationTest/resources/expected_jars.txt
+++ b/geode-assembly/src/integrationTest/resources/expected_jars.txt
@@ -79,6 +79,7 @@ joda-time
jopt-simple
json-path
json-smart
+jspecify
jul-to-slf4j
lang-tag
log4j-api
diff --git a/geode-assembly/src/integrationTest/resources/gfsh_dependency_classpath.txt b/geode-assembly/src/integrationTest/resources/gfsh_dependency_classpath.txt
index 68a5913622f0..40e811d4495c 100644
--- a/geode-assembly/src/integrationTest/resources/gfsh_dependency_classpath.txt
+++ b/geode-assembly/src/integrationTest/resources/gfsh_dependency_classpath.txt
@@ -76,7 +76,7 @@ commons-digester-2.1.jar
commons-io-2.19.0.jar
commons-logging-1.3.5.jar
classgraph-4.8.147.jar
-micrometer-core-1.15.12.jar
+micrometer-core-1.16.7.jar
HdrHistogram-2.2.2.jar
fastutil-8.5.8.jar
jakarta.resource-api-2.1.0.jar
@@ -124,9 +124,10 @@ jline-reader-3.26.3.jar
jline-style-3.26.3.jar
jline-terminal-3.26.3.jar
jline-native-3.26.3.jar
-micrometer-observation-1.15.12.jar
+micrometer-observation-1.16.7.jar
spring-jcl-6.1.21.jar
-micrometer-commons-1.15.12.jar
+micrometer-commons-1.16.7.jar
+jspecify-1.0.1.jar
LatencyUtils-2.0.3.jar
snakeyaml-2.5.jar
reactor-core-3.6.10.jar
diff --git a/geode-core/src/main/java/org/apache/geode/metrics/internal/NoopMeterRegistry.java b/geode-core/src/main/java/org/apache/geode/metrics/internal/NoopMeterRegistry.java
index bd70d1d99240..b93f069a6bc4 100644
--- a/geode-core/src/main/java/org/apache/geode/metrics/internal/NoopMeterRegistry.java
+++ b/geode-core/src/main/java/org/apache/geode/metrics/internal/NoopMeterRegistry.java
@@ -39,13 +39,10 @@
import io.micrometer.core.instrument.noop.NoopLongTaskTimer;
import io.micrometer.core.instrument.noop.NoopMeter;
import io.micrometer.core.instrument.noop.NoopTimer;
-import io.micrometer.core.lang.NonNullApi;
-import io.micrometer.core.lang.Nullable;
import org.apache.geode.annotations.Immutable;
import org.apache.geode.annotations.VisibleForTesting;
-@NonNullApi
public class NoopMeterRegistry extends MeterRegistry {
@Immutable
@@ -71,7 +68,7 @@ private NoopMeterRegistry(Clock clock) {
}
@Override
- protected Gauge newGauge(Meter.Id id, @Nullable T obj, ToDoubleFunction valueFunction) {
+ protected Gauge newGauge(Meter.Id id, T obj, ToDoubleFunction valueFunction) {
return new NoopGauge(id);
}
diff --git a/geode-server-all/src/integrationTest/resources/dependency_classpath.txt b/geode-server-all/src/integrationTest/resources/dependency_classpath.txt
index 8ba910109e4d..c1d33d9a29f9 100644
--- a/geode-server-all/src/integrationTest/resources/dependency_classpath.txt
+++ b/geode-server-all/src/integrationTest/resources/dependency_classpath.txt
@@ -45,7 +45,7 @@ spring-shell-standard-commands-3.3.3.jar
spring-shell-standard-3.3.3.jar
spring-shell-core-3.3.3.jar
commons-io-2.19.0.jar
-micrometer-core-1.15.12.jar
+micrometer-core-1.16.7.jar
jakarta.resource-api-2.1.0.jar
jetty-ee10-annotations-12.0.37.jar
spring-boot-starter-validation-3.3.13.jar
@@ -124,8 +124,9 @@ jline-reader-3.26.3.jar
jline-style-3.26.3.jar
jline-terminal-3.26.3.jar
jline-native-3.26.3.jar
-micrometer-observation-1.15.12.jar
-micrometer-commons-1.15.12.jar
+micrometer-observation-1.16.7.jar
+micrometer-commons-1.16.7.jar
+jspecify-1.0.1.jar
LatencyUtils-2.0.3.jar
snakeyaml-2.5.jar
spring-jcl-6.1.21.jar
From 7d317c24ffc11bcb05230e21815486e643d8d9b0 Mon Sep 17 00:00:00 2001
From: Jinwoo Hwang <92374539+JinwooHwang@users.noreply.github.com>
Date: Thu, 3 Sep 2026 20:25:16 -0400
Subject: [PATCH 2/9] [GEODE-10621] Remediation of CVE-2026-19032: Jackson
2.21.5 to 2.21.6 (#8048)
---
boms/geode-all-bom/src/test/resources/expected-pom.xml | 8 ++++----
.../geode/gradle/plugins/DependencyConstraints.groovy | 4 ++--
.../src/integrationTest/resources/assembly_content.txt | 10 +++++-----
.../resources/gfsh_dependency_classpath.txt | 10 +++++-----
.../integrationTest/resources/dependency_classpath.txt | 10 +++++-----
5 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/boms/geode-all-bom/src/test/resources/expected-pom.xml b/boms/geode-all-bom/src/test/resources/expected-pom.xml
index d40e19ff4966..84d481c32760 100644
--- a/boms/geode-all-bom/src/test/resources/expected-pom.xml
+++ b/boms/geode-all-bom/src/test/resources/expected-pom.xml
@@ -475,22 +475,22 @@
com.fasterxml.jackson.corejackson-core
- 2.21.5
+ 2.21.6com.fasterxml.jackson.corejackson-databind
- 2.21.5
+ 2.21.6com.fasterxml.jackson.datatypejackson-datatype-joda
- 2.21.5
+ 2.21.6com.fasterxml.jackson.datatypejackson-datatype-jsr310
- 2.21.5
+ 2.21.6com.jayway.jsonpath
diff --git a/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy b/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
index 72ae49070199..1cb70b31d45d 100644
--- a/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
+++ b/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
@@ -55,9 +55,9 @@ class DependencyConstraints {
deps.put("slf4j-api.version", "2.0.18")
deps.put("jakarta.transaction-api.version", "2.0.1")
deps.put("jboss-modules.version", "1.11.0.Final")
- deps.put("jackson.version", "2.21.5")
+ deps.put("jackson.version", "2.21.6")
deps.put("jackson.annotations.version", "2.21")
- deps.put("jackson.databind.version", "2.21.5")
+ deps.put("jackson.databind.version", "2.21.6")
// Spring Framework 6.x Migration
deps.put("springshell.version", "3.3.3")
deps.put("springframework.version", "6.1.21")
diff --git a/geode-assembly/src/integrationTest/resources/assembly_content.txt b/geode-assembly/src/integrationTest/resources/assembly_content.txt
index eb63cebdf6a6..52491b4aaa4e 100644
--- a/geode-assembly/src/integrationTest/resources/assembly_content.txt
+++ b/geode-assembly/src/integrationTest/resources/assembly_content.txt
@@ -965,11 +965,11 @@ lib/httpcore5-5.4.3.jar
lib/httpcore5-h2-5.4.3.jar
lib/istack-commons-runtime-4.1.1.jar
lib/jackson-annotations-2.21.jar
-lib/jackson-core-2.21.5.jar
-lib/jackson-databind-2.21.5.jar
-lib/jackson-dataformat-yaml-2.21.5.jar
-lib/jackson-datatype-joda-2.21.5.jar
-lib/jackson-datatype-jsr310-2.21.5.jar
+lib/jackson-core-2.21.6.jar
+lib/jackson-databind-2.21.6.jar
+lib/jackson-dataformat-yaml-2.21.6.jar
+lib/jackson-datatype-joda-2.21.6.jar
+lib/jackson-datatype-jsr310-2.21.6.jar
lib/jakarta.activation-api-2.1.3.jar
lib/jakarta.annotation-api-2.1.1.jar
lib/jakarta.el-api-5.0.0.jar
diff --git a/geode-assembly/src/integrationTest/resources/gfsh_dependency_classpath.txt b/geode-assembly/src/integrationTest/resources/gfsh_dependency_classpath.txt
index 40e811d4495c..f4770a5f02dc 100644
--- a/geode-assembly/src/integrationTest/resources/gfsh_dependency_classpath.txt
+++ b/geode-assembly/src/integrationTest/resources/gfsh_dependency_classpath.txt
@@ -21,11 +21,11 @@ spring-shell-starter-3.3.3.jar
spring-web-6.1.21.jar
commons-lang3-3.18.0.jar
rmiio-2.1.2.jar
-jackson-datatype-jsr310-2.21.5.jar
-jackson-datatype-joda-2.21.5.jar
-jackson-core-2.21.5.jar
-jackson-dataformat-yaml-2.21.5.jar
-jackson-databind-2.21.5.jar
+jackson-datatype-jsr310-2.21.6.jar
+jackson-datatype-joda-2.21.6.jar
+jackson-core-2.21.6.jar
+jackson-dataformat-yaml-2.21.6.jar
+jackson-databind-2.21.6.jar
swagger-annotations-2.2.22.jar
jaxb-runtime-4.0.2.jar
jaxb-core-4.0.2.jar
diff --git a/geode-server-all/src/integrationTest/resources/dependency_classpath.txt b/geode-server-all/src/integrationTest/resources/dependency_classpath.txt
index c1d33d9a29f9..cf760a03c5ea 100644
--- a/geode-server-all/src/integrationTest/resources/dependency_classpath.txt
+++ b/geode-server-all/src/integrationTest/resources/dependency_classpath.txt
@@ -19,11 +19,11 @@ geode-unsafe-0.0.0.jar
geode-deployment-legacy-0.0.0.jar
snappy-0.5.jar
swagger-annotations-2.2.22.jar
-jackson-datatype-jsr310-2.21.5.jar
-jackson-dataformat-yaml-2.21.5.jar
-jackson-core-2.21.5.jar
-jackson-datatype-joda-2.21.5.jar
-jackson-databind-2.21.5.jar
+jackson-datatype-jsr310-2.21.6.jar
+jackson-dataformat-yaml-2.21.6.jar
+jackson-core-2.21.6.jar
+jackson-datatype-joda-2.21.6.jar
+jackson-databind-2.21.6.jar
httpclient5-5.4.4.jar
httpcore5-h2-5.4.3.jar
httpcore5-5.4.3.jar
From 3c26a210e5ab58e8d39403fca156b4c2ca46ba5f Mon Sep 17 00:00:00 2001
From: Jinwoo Hwang <92374539+JinwooHwang@users.noreply.github.com>
Date: Thu, 3 Sep 2026 20:56:47 -0400
Subject: [PATCH 3/9] [GEODE-10622] Remediation of CVE-2026-47857: Reactor Core
3.6.10 to 3.8.7 (#8049)
---
.../apache/geode/gradle/plugins/DependencyConstraints.groovy | 5 +++++
.../src/integrationTest/resources/assembly_content.txt | 2 +-
.../integrationTest/resources/gfsh_dependency_classpath.txt | 2 +-
.../src/integrationTest/resources/dependency_classpath.txt | 2 +-
4 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy b/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
index 1cb70b31d45d..a21a6e558fdb 100644
--- a/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
+++ b/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
@@ -67,6 +67,9 @@ class DependencyConstraints {
deps.put("springldap.version", "3.2.7")
deps.put("springdoc.version", "2.6.0")
+ // Pin Reactor Core (transitive via spring-shell-core) to a fixed version
+ deps.put("reactor-core.version", "3.8.7")
+
// These version numbers are used in testing various versions of tomcat and are consumed explicitly
// in will be called explicitly in the relevant extensions module, and respective configurations
// in geode-assembly.gradle. Moreover, dependencyManagement does not seem to play nicely when
@@ -149,6 +152,8 @@ class DependencyConstraints {
api(group: 'io.github.resilience4j', name: 'resilience4j-retry', version: '1.7.1')
api(group: 'io.lettuce', name: 'lettuce-core', version: '6.1.8.RELEASE')
api(group: 'io.micrometer', name: 'micrometer-core', version: get('micrometer.version'))
+ // Pin Reactor Core (pulled in via spring-shell-core) to 3.8.7
+ api(group: 'io.projectreactor', name: 'reactor-core', version: get('reactor-core.version'))
api(group: 'io.swagger.core.v3', name: 'swagger-annotations', version: '2.2.22')
api(group: 'org.hdrhistogram', name: 'HdrHistogram', version: '2.2.2')
api(group: 'it.unimi.dsi', name: 'fastutil', version: get('fastutil.version'))
diff --git a/geode-assembly/src/integrationTest/resources/assembly_content.txt b/geode-assembly/src/integrationTest/resources/assembly_content.txt
index 52491b4aaa4e..2f88d6a0f2c8 100644
--- a/geode-assembly/src/integrationTest/resources/assembly_content.txt
+++ b/geode-assembly/src/integrationTest/resources/assembly_content.txt
@@ -1033,7 +1033,7 @@ lib/mx4j-remote-3.0.2.jar
lib/mx4j-tools-3.0.1.jar
lib/ra.jar
lib/reactive-streams-1.0.4.jar
-lib/reactor-core-3.6.10.jar
+lib/reactor-core-3.8.7.jar
lib/rmiio-2.1.2.jar
lib/shiro-cache-3.0.0.jar
lib/shiro-config-core-3.0.0.jar
diff --git a/geode-assembly/src/integrationTest/resources/gfsh_dependency_classpath.txt b/geode-assembly/src/integrationTest/resources/gfsh_dependency_classpath.txt
index f4770a5f02dc..8a26d0186bf1 100644
--- a/geode-assembly/src/integrationTest/resources/gfsh_dependency_classpath.txt
+++ b/geode-assembly/src/integrationTest/resources/gfsh_dependency_classpath.txt
@@ -130,7 +130,7 @@ micrometer-commons-1.16.7.jar
jspecify-1.0.1.jar
LatencyUtils-2.0.3.jar
snakeyaml-2.5.jar
-reactor-core-3.6.10.jar
+reactor-core-3.8.7.jar
ST4-4.3.3.jar
txw2-4.0.2.jar
asm-commons-9.10.1.jar
diff --git a/geode-server-all/src/integrationTest/resources/dependency_classpath.txt b/geode-server-all/src/integrationTest/resources/dependency_classpath.txt
index cf760a03c5ea..d0c9eccab8aa 100644
--- a/geode-server-all/src/integrationTest/resources/dependency_classpath.txt
+++ b/geode-server-all/src/integrationTest/resources/dependency_classpath.txt
@@ -134,7 +134,7 @@ asm-commons-9.10.1.jar
asm-tree-9.10.1.jar
asm-9.10.1.jar
txw2-4.0.2.jar
-reactor-core-3.6.10.jar
+reactor-core-3.8.7.jar
ST4-4.3.3.jar
jakarta.enterprise.lang-model-4.0.1.jar
reactive-streams-1.0.4.jar
From 3c135beebd4a5b3414aac68e9ba04144edd9c7cf Mon Sep 17 00:00:00 2001
From: Jinwoo Hwang <92374539+JinwooHwang@users.noreply.github.com>
Date: Wed, 26 Aug 2026 10:35:45 -0400
Subject: [PATCH 4/9] [GEODE-10618] Make named-query endpoint permissions
consistent with region data endpoints (#8043)
---
.../web/RestSecurityIntegrationTest.java | 4 +
.../web/RestSecurityPostProcessorTest.java | 2 +-
...ueryAccessControllerAuthorizationTest.java | 192 ++++++++++++++++++
.../controllers/QueryAccessController.java | 4 +-
4 files changed, 199 insertions(+), 3 deletions(-)
create mode 100644 geode-web-api/src/integrationTest/java/org/apache/geode/rest/internal/web/controllers/QueryAccessControllerAuthorizationTest.java
diff --git a/geode-assembly/src/integrationTest/java/org/apache/geode/rest/internal/web/RestSecurityIntegrationTest.java b/geode-assembly/src/integrationTest/java/org/apache/geode/rest/internal/web/RestSecurityIntegrationTest.java
index 2a9bc83f35e1..7a0a97fe7242 100644
--- a/geode-assembly/src/integrationTest/java/org/apache/geode/rest/internal/web/RestSecurityIntegrationTest.java
+++ b/geode-assembly/src/integrationTest/java/org/apache/geode/rest/internal/web/RestSecurityIntegrationTest.java
@@ -107,6 +107,8 @@ public void testPostQuery() {
assertResponse(restClient.doPost("/queries?id=0&q=", "user", "user", ""))
.hasStatusCode(403);
assertResponse(restClient.doPost("/queries?id=0&q=", "dataRead", "dataRead", ""))
+ .hasStatusCode(403);
+ assertResponse(restClient.doPost("/queries?id=0&q=", "dataWrite", "dataWrite", ""))
.hasStatusCode(500);
}
@@ -127,6 +129,8 @@ public void testPutQuery() {
assertResponse(restClient.doPut("/queries/id", "user", "user", "{\"id\" : \"foo\"}"))
.hasStatusCode(403);
assertResponse(restClient.doPut("/queries/id", "dataRead", "dataRead", "{\"id\" : \"foo\"}"))
+ .hasStatusCode(403);
+ assertResponse(restClient.doPut("/queries/id", "dataWrite", "dataWrite", "{\"id\" : \"foo\"}"))
.hasStatusCode(404);
}
diff --git a/geode-assembly/src/integrationTest/java/org/apache/geode/rest/internal/web/RestSecurityPostProcessorTest.java b/geode-assembly/src/integrationTest/java/org/apache/geode/rest/internal/web/RestSecurityPostProcessorTest.java
index a30855ac3631..cd5a3fd73004 100644
--- a/geode-assembly/src/integrationTest/java/org/apache/geode/rest/internal/web/RestSecurityPostProcessorTest.java
+++ b/geode-assembly/src/integrationTest/java/org/apache/geode/rest/internal/web/RestSecurityPostProcessorTest.java
@@ -158,7 +158,7 @@ public void namedQuery() throws Exception {
// Install the named query
assertResponse(
restClient.doPost("/queries?id=selectCustomer&q=" + URLEncoder.encode(namedQuery, "UTF-8"),
- "dataReader", "1234567", ""))
+ "dataUser", "1234567", ""))
.hasStatusCode(201);
// Verify the query has been installed
diff --git a/geode-web-api/src/integrationTest/java/org/apache/geode/rest/internal/web/controllers/QueryAccessControllerAuthorizationTest.java b/geode-web-api/src/integrationTest/java/org/apache/geode/rest/internal/web/controllers/QueryAccessControllerAuthorizationTest.java
new file mode 100644
index 000000000000..72e0db3fbb90
--- /dev/null
+++ b/geode-web-api/src/integrationTest/java/org/apache/geode/rest/internal/web/controllers/QueryAccessControllerAuthorizationTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.rest.internal.web.controllers;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
+import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.GenericXmlWebContextLoader;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.context.web.WebMergedContextConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.RequestPostProcessor;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.GenericWebApplicationContext;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.RegionShortcut;
+import org.apache.geode.cache.internal.HttpService;
+import org.apache.geode.examples.SimpleSecurityManager;
+import org.apache.geode.management.internal.RestAgent;
+import org.apache.geode.test.junit.rules.ServerStarterRule;
+
+/**
+ * Verifies the permissions the named-query endpoints of {@link QueryAccessController} require.
+ *
+ *
+ * The endpoints that read query state require {@code DATA:READ}; the endpoints that create, update
+ * or remove a stored named query all require {@code DATA:WRITE}, matching the permission required
+ * for the equivalent operations on ordinary region data.
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = {"classpath*:WEB-INF/geode-servlet.xml"},
+ loader = SecuredTestContextLoader.class)
+@WebAppConfiguration
+public class QueryAccessControllerAuthorizationTest {
+
+ private static final String QUERY_STORE = "__ParameterizedQueries__";
+ private static final String REGION_NAME = "customers";
+
+ private static final String READ_USER = "dataRead";
+ private static final String WRITE_USER = "dataWrite";
+
+ private static final String OQL = "SELECT * FROM " + Region.SEPARATOR + REGION_NAME;
+ private static final String OTHER_OQL =
+ "SELECT c.name FROM " + Region.SEPARATOR + REGION_NAME + " c";
+
+ private static final RequestPostProcessor JSON = new JsonRequestPostProcessor();
+
+ @ClassRule
+ public static ServerStarterRule rule = new ServerStarterRule()
+ .withProperty("log-level", "warn")
+ .withSecurityManager(SimpleSecurityManager.class)
+ .withRegion(RegionShortcut.REPLICATE, REGION_NAME);
+
+ @Autowired
+ private WebApplicationContext webApplicationContext;
+
+ private MockMvc mockMvc;
+
+ @BeforeClass
+ public static void createQueryStore() {
+ RestAgent.createParameterizedQueryRegion();
+ }
+
+ @Before
+ public void setUp() {
+ mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
+ .apply(springSecurity())
+ .build();
+ queryStore().clear();
+ }
+
+ private static Region queryStore() {
+ return rule.getCache().getInternalRegionByPath(Region.SEPARATOR + QUERY_STORE);
+ }
+
+ @Test
+ public void createIsRefusedForAUserWithoutWritePermission() throws Exception {
+ mockMvc.perform(post("/v1/queries?id=q1&q=" + OQL)
+ .with(httpBasic(READ_USER, READ_USER))
+ .with(JSON))
+ .andExpect(status().isForbidden());
+
+ assertThat(queryStore()).doesNotContainKey("q1");
+ }
+
+ @Test
+ public void updateIsRefusedForAUserWithoutWritePermission() throws Exception {
+ queryStore().put("q1", OQL);
+
+ mockMvc.perform(put("/v1/queries/q1?q=" + OTHER_OQL)
+ .with(httpBasic(READ_USER, READ_USER))
+ .with(JSON))
+ .andExpect(status().isForbidden());
+
+ assertThat(queryStore().get("q1")).isEqualTo(OQL);
+ }
+
+ @Test
+ public void deleteIsRefusedForAUserWithoutWritePermission() throws Exception {
+ queryStore().put("q1", OQL);
+
+ mockMvc.perform(delete("/v1/queries/q1")
+ .with(httpBasic(READ_USER, READ_USER))
+ .with(JSON))
+ .andExpect(status().isForbidden());
+
+ assertThat(queryStore()).containsKey("q1");
+ }
+
+ @Test
+ public void createAndUpdateAreAllowedForAUserWithWritePermission() throws Exception {
+ mockMvc.perform(post("/v1/queries?id=q1&q=" + OQL)
+ .with(httpBasic(WRITE_USER, WRITE_USER))
+ .with(JSON))
+ .andExpect(status().isCreated());
+
+ assertThat(queryStore().get("q1")).isEqualTo(OQL);
+
+ mockMvc.perform(put("/v1/queries/q1?q=" + OTHER_OQL)
+ .with(httpBasic(WRITE_USER, WRITE_USER))
+ .with(JSON))
+ .andExpect(status().isOk());
+
+ assertThat(queryStore().get("q1")).isEqualTo(OTHER_OQL);
+ }
+
+ @Test
+ public void listIsAllowedForAUserWithReadPermission() throws Exception {
+ queryStore().put("q1", OQL);
+
+ mockMvc.perform(get("/v1/queries")
+ .with(httpBasic(READ_USER, READ_USER))
+ .with(JSON))
+ .andExpect(status().isOk());
+ }
+
+ private static class JsonRequestPostProcessor implements RequestPostProcessor {
+
+ @SuppressWarnings("deprecation")
+ private static final MediaType APPLICATION_JSON_UTF8 = MediaType.APPLICATION_JSON_UTF8;
+
+ @Override
+ public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
+ request.addHeader(HttpHeaders.ACCEPT, APPLICATION_JSON_UTF8);
+ request.addHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON_UTF8);
+ return request;
+ }
+ }
+}
+
+
+class SecuredTestContextLoader extends GenericXmlWebContextLoader {
+ @Override
+ protected void loadBeanDefinitions(GenericWebApplicationContext context,
+ WebMergedContextConfiguration webMergedConfig) {
+ super.loadBeanDefinitions(context, webMergedConfig);
+ context.getServletContext().setAttribute(
+ HttpService.SECURITY_SERVICE_SERVLET_CONTEXT_PARAM,
+ QueryAccessControllerAuthorizationTest.rule.getCache().getSecurityService());
+ }
+}
diff --git a/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/QueryAccessController.java b/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/QueryAccessController.java
index 5540ba455de7..42071306eb5f 100644
--- a/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/QueryAccessController.java
+++ b/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/QueryAccessController.java
@@ -129,7 +129,7 @@ public ResponseEntity> list() {
@ApiResponse(responseCode = "403", description = "Insufficient privileges for operation."),
@ApiResponse(responseCode = "409", description = "QueryId already assigned to other query."),
@ApiResponse(responseCode = "500", description = "GemFire throws an error or exception.")})
- @PreAuthorize("@securityService.authorizeBoolean('DATA', 'READ')")
+ @PreAuthorize("@securityService.authorizeBoolean('DATA', 'WRITE')")
public ResponseEntity> create(@RequestParam("id") final String queryId,
@RequestParam(value = "q", required = false) String oqlInUrl,
@RequestBody(required = false) final String oqlInBody) {
@@ -311,7 +311,7 @@ public ResponseEntity runNamedQuery(@PathVariable("query") String queryI
@ApiResponse(responseCode = "403", description = "Insufficient privileges for operation."),
@ApiResponse(responseCode = "404", description = "queryId does not exist."),
@ApiResponse(responseCode = "500", description = "GemFire throws an error or exception.")})
- @PreAuthorize("@securityService.authorizeBoolean('DATA', 'READ')")
+ @PreAuthorize("@securityService.authorizeBoolean('DATA', 'WRITE')")
public ResponseEntity> update(@PathVariable("query") final String queryId,
@RequestParam(value = "q", required = false) String oqlInUrl,
@RequestBody(required = false) final String oqlInBody) {
From 634944d8f9cf4eb5d2861c1849de8f0f6978359a Mon Sep 17 00:00:00 2001
From: Jinwoo Hwang <92374539+JinwooHwang@users.noreply.github.com>
Date: Wed, 26 Aug 2026 10:36:30 -0400
Subject: [PATCH 5/9] GEODE-10613: Add configurable export directories for
export data (#8042)
---
.../implementing_authorization.html.md.erb | 2 +-
.../gfsh/command-pages/export.html.md.erb | 16 ++
.../commands/ExportDataIntegrationTest.java | 7 +
.../commands/ImportDataIntegrationTest.java | 7 +
.../cli/commands/ExportDataCommand.java | 26 +++
.../cli/functions/ExportDataFunction.java | 60 ++++++-
.../cli/commands/ExportDataCommandTest.java | 32 ++++
.../ExportDataDirectoryConfigTest.java | 169 ++++++++++++++++++
.../internal/security/TestCommand.java | 2 +-
9 files changed, 317 insertions(+), 4 deletions(-)
create mode 100644 geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/functions/ExportDataDirectoryConfigTest.java
diff --git a/geode-docs/security/implementing_authorization.html.md.erb b/geode-docs/security/implementing_authorization.html.md.erb
index 37dcb917394c..d30712d29370 100644
--- a/geode-docs/security/implementing_authorization.html.md.erb
+++ b/geode-docs/security/implementing_authorization.html.md.erb
@@ -154,7 +154,7 @@ This table classifies the permissions assigned for `gfsh` operations.
| execute function | Defaults to DATA:WRITE. Override `Function.getRequiredPermissions` to change the permission. |
| export cluster-configuration | CLUSTER:READ |
| export config | CLUSTER:READ |
-| export data | CLUSTER:READ |
+| export data | DATA:READ:RegionName and CLUSTER:WRITE |
| export logs | CLUSTER:READ |
| export offline-disk-store | CLUSTER:READ |
| export stack-traces | CLUSTER:READ |
diff --git a/geode-docs/tools_modules/gfsh/command-pages/export.html.md.erb b/geode-docs/tools_modules/gfsh/command-pages/export.html.md.erb
index 0fc1a76be7ad..4c360119ef7f 100644
--- a/geode-docs/tools_modules/gfsh/command-pages/export.html.md.erb
+++ b/geode-docs/tools_modules/gfsh/command-pages/export.html.md.erb
@@ -165,6 +165,22 @@ In this scenario, partitioned region data is exported simultaneously on all host
| ‑‑dir | Directory to which the exported data is to be written. Required if ‑‑parallel is true. Cannot be specified at the same time as ‑‑file.|
| ‑‑parallel | Export local data on each node to a directory on that machine. Available for partitioned regions only. |
+**Export locations:**
+
+The snapshot is written by the member named in `--member`, on that member's host. A member writes
+exports into its own working directory (and sub-directories of it). To export somewhere else, such
+as a mounted backup location, set the `gemfire.export.data.dirs` system property on the member to
+the additional directories, separated by the platform's path separator:
+
+``` pre
+-Dgemfire.export.data.dirs=/mnt/backup/geode:/var/exports/geode
+```
+
+A path containing a `..` segment is not accepted, and a path that resolves outside the configured
+directories is rejected by the member.
+
+**Required permission:** `DATA:READ` on the exported region, plus `CLUSTER:WRITE`.
+
**Example Commands:**
``` pre
diff --git a/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ExportDataIntegrationTest.java b/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ExportDataIntegrationTest.java
index 80082f15f167..86e800d56058 100644
--- a/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ExportDataIntegrationTest.java
+++ b/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ExportDataIntegrationTest.java
@@ -17,6 +17,7 @@
package org.apache.geode.management.internal.cli.commands;
import static org.apache.geode.cache.Region.SEPARATOR;
+import static org.apache.geode.management.internal.cli.functions.ExportDataFunction.EXPORT_DATA_DIRS_PROPERTY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertFalse;
@@ -31,6 +32,7 @@
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.contrib.java.lang.system.RestoreSystemProperties;
import org.junit.rules.TemporaryFolder;
import org.apache.geode.DataSerializable;
@@ -58,6 +60,9 @@ public class ExportDataIntegrationTest {
@Rule
public TemporaryFolder tempDir = new TemporaryFolder();
+ @Rule
+ public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
+
private Region region;
private Path snapshotFile;
private Path snapshotDir;
@@ -87,6 +92,8 @@ public void setup() throws Exception {
region = server.getCache().getRegion(TEST_REGION_NAME);
loadRegion("value");
Path basePath = tempDir.getRoot().toPath();
+ // configure the test's temporary folder as an export destination
+ System.setProperty(EXPORT_DATA_DIRS_PROPERTY, basePath.toString());
snapshotFile = basePath.resolve(SNAPSHOT_FILE);
snapshotDir = basePath.resolve(SNAPSHOT_DIR);
}
diff --git a/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ImportDataIntegrationTest.java b/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ImportDataIntegrationTest.java
index 63fb1461bf4c..2317ce847c91 100644
--- a/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ImportDataIntegrationTest.java
+++ b/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ImportDataIntegrationTest.java
@@ -17,6 +17,7 @@
package org.apache.geode.management.internal.cli.commands;
import static org.apache.geode.cache.Region.SEPARATOR;
+import static org.apache.geode.management.internal.cli.functions.ExportDataFunction.EXPORT_DATA_DIRS_PROPERTY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
@@ -30,6 +31,7 @@
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.contrib.java.lang.system.RestoreSystemProperties;
import org.junit.rules.TemporaryFolder;
import org.apache.geode.cache.Region;
@@ -55,6 +57,9 @@ public class ImportDataIntegrationTest {
@Rule
public TemporaryFolder tempDir = new TemporaryFolder();
+ @Rule
+ public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
+
private Region region;
private Path snapshotFile;
private Path snapshotDir;
@@ -65,6 +70,8 @@ public void setup() throws Exception {
region = server.getCache().getRegion(TEST_REGION_NAME);
loadRegion("value");
Path basePath = tempDir.getRoot().toPath();
+ // configure the test's temporary folder as an export destination
+ System.setProperty(EXPORT_DATA_DIRS_PROPERTY, basePath.toString());
snapshotFile = basePath.resolve(SNAPSHOT_FILE);
snapshotDir = basePath.resolve(SNAPSHOT_DIR);
}
diff --git a/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/ExportDataCommand.java b/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/ExportDataCommand.java
index 9892ceef5f3d..385d2c3e5242 100644
--- a/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/ExportDataCommand.java
+++ b/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/ExportDataCommand.java
@@ -16,6 +16,8 @@
package org.apache.geode.management.internal.cli.commands;
import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
@@ -33,6 +35,7 @@
import org.apache.geode.management.internal.cli.result.model.ResultModel;
import org.apache.geode.management.internal.functions.CliFunctionResult;
import org.apache.geode.management.internal.i18n.CliStrings;
+import org.apache.geode.security.ResourcePermission;
import org.apache.geode.security.ResourcePermission.Operation;
import org.apache.geode.security.ResourcePermission.Resource;
@@ -54,6 +57,7 @@ public ResultModel exportData(
help = CliStrings.EXPORT_DATA__PARALLEL_HELP) boolean parallel) {
authorize(Resource.DATA, Operation.READ, regionName);
+ authorize(Resource.CLUSTER, Operation.WRITE, ResourcePermission.ALL);
final DistributedMember targetMember = getMember(memberNameOrId);
Optional validationResult = validatePath(filePath, dirPath, parallel);
@@ -100,6 +104,28 @@ private Optional validatePath(String filePath, String dirPath, bool
return Optional.of(ResultModel.createError(CliStrings.format(
CliStrings.INVALID_FILE_EXTENSION, CliStrings.GEODE_DATA_FILE_EXTENSION)));
}
+
+ if (filePath != null && containsParentDirectorySegment(filePath)) {
+ return Optional.of(invalidPathError(CliStrings.EXPORT_DATA__FILE, filePath));
+ }
+ if (dirPath != null && containsParentDirectorySegment(dirPath)) {
+ return Optional.of(invalidPathError(CliStrings.EXPORT_DATA__DIR, dirPath));
+ }
+
return Optional.empty();
}
+
+ private static boolean containsParentDirectorySegment(String path) {
+ for (Path element : Paths.get(path)) {
+ if ("..".equals(element.toString())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static ResultModel invalidPathError(String option, String path) {
+ return ResultModel.createError(String.format(
+ "Option \"%s\" must not contain a \"..\" path segment: %s", option, path));
+ }
}
diff --git a/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/functions/ExportDataFunction.java b/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/functions/ExportDataFunction.java
index 0c83d40a8ae6..2f0a18174721 100644
--- a/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/functions/ExportDataFunction.java
+++ b/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/functions/ExportDataFunction.java
@@ -15,6 +15,9 @@
package org.apache.geode.management.internal.cli.functions;
import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.Region;
@@ -27,12 +30,15 @@
import org.apache.geode.management.cli.CliFunction;
import org.apache.geode.management.internal.functions.CliFunctionResult;
import org.apache.geode.management.internal.i18n.CliStrings;
+import org.apache.geode.util.internal.GeodeGlossary;
/***
* Function which carries out the export of a region to a file on a member. Uses the
* RegionSnapshotService to export the data
*
- *
+ *
+ * Export destinations are resolved to their canonical form and must be within the export
+ * directories configured for this member.
*/
public class ExportDataFunction extends CliFunction {
private static final long serialVersionUID = 1L;
@@ -40,6 +46,18 @@ public class ExportDataFunction extends CliFunction {
private static final String ID =
"org.apache.geode.management.internal.cli.functions.ExportDataFunction";
+ /**
+ * System property naming additional directories this member writes {@code export data} snapshots
+ * into. Several directories may be listed, separated by {@link File#pathSeparator}. Exports into
+ * sub-directories of a configured directory are included.
+ *
+ *
+ * The member's working directory is always configured, since that is where a relative export
+ * path resolves to, so when this property is not set it is the only export destination.
+ */
+ public static final String EXPORT_DATA_DIRS_PROPERTY =
+ GeodeGlossary.GEMFIRE_PREFIX + "export.data.dirs";
+
@Override
public String getId() {
return ID;
@@ -62,7 +80,7 @@ public CliFunctionResult executeFunction(FunctionContext context) thro
String hostName = cache.getDistributedSystem().getDistributedMember().getHost();
if (region != null) {
RegionSnapshotService