diff --git a/.github/workflows/security-release-gate.yml b/.github/workflows/security-release-gate.yml index 5b79f7d9d6..4c30d634e9 100644 --- a/.github/workflows/security-release-gate.yml +++ b/.github/workflows/security-release-gate.yml @@ -57,6 +57,16 @@ jobs: import xml.etree.ElementTree as ET required = { + 'io.cattle.platform.schema.processor.GenericObjectAuthOverlayTest': { + 'lowRolesHideCapabilitiesAtTheSharedResponseBoundary', + 'inheritedStorageFieldsCannotReintroduceCapabilities', + 'privilegedProjectClientsKeepPluginStorage', + 'pullTaskStorageRemainsPrivateWithoutRemovingTypedStatus', + }, + 'io.cattle.platform.resources.FrozenGenericObjectRoleSchemaTest': { + 'lowRoleFrozenSchemasCannotRevealGenericStorageCapabilities', + 'privilegedProjectSchemasRetainPluginStorage', + }, 'io.github.ibuildthecloud.gdapi.request.handler.BodyParserRequestHandlerTest': { 'mergeMapKeepsRequestParamsAndLetsBodyOverride', 'mergeListRecursivelyMergesAllowedMapItems', @@ -77,7 +87,9 @@ jobs: } suites = [] observed = {} - for path in sorted(Path('code').glob('**/target/surefire-reports/TEST-*.xml')): + reports = list(Path('code').glob('**/target/surefire-reports/TEST-*.xml')) + reports += list(Path('resources').glob('**/target/surefire-reports/TEST-*.xml')) + for path in sorted(reports): root = ET.parse(path).getroot() cases = [] for case in root.findall('testcase'): diff --git a/README.md b/README.md index 78336a36e9..faa12f8c60 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,13 @@ preserved upstream boundary. ## Current release +The source tree prepares `v0.183.329`, which closes a low-role GenericObject +read bypass for plugin capabilities in both v2-beta and the frozen v1 schemas. +Readonly/restricted clients retain resource metadata; plugin configuration is +read through its typed API. Owner/member/service storage remains unchanged. +See the [329 release note](docs/releases/orchestration-engine-0.183.329.md). +This source change is not yet a published artifact or accepted Server deployment. + The current published release is `v0.183.328`. It updates platform Jackson to `2.22.3` and the isolated WebAuthn/logging runtime to `3.2.3`, and consumes the official Cache `5.7.5` artifact. The two Jackson namespaces remain separate. @@ -101,7 +108,7 @@ bash scripts/check-cattle-jdk25-full-package After the gate passes, package and check the release artifact: ```sh -ENGINE_VERSION=0.183.328 bash scripts/build --release +ENGINE_VERSION=0.183.329 bash scripts/build --release bash scripts/check-release-artifact dist/artifacts/cattle.jar ``` @@ -111,6 +118,23 @@ database and platform checks require isolated MariaDB/MySQL and companion services. See [COMPATIBILITY.md](COMPATIBILITY.md), [SECURITY.md](SECURITY.md), and [ORIGIN.md](ORIGIN.md) for those boundaries and source provenance. +For a deliberate frozen-schema source migration, first run +`GenericObjectAuthOverlayTest` with the supported Maven/JDK toolchain. Use its +Surefire `java.class.path` property to run +From the repository root, run +`java --class-path scripts/java/UpdateFrozenGenericObjectSchemas.java`. +The one-time producer accepts no path arguments, requires the reviewed 328 +snapshot SHA-256 values before deserialization, and rejects unexpected classes +with a bounded serialization filter. `--check-filter` verifies the current two +role snapshots and rejects a graph with a deserialization callback without +executing it; it does not write files. Already migrated snapshots are rejected +by the migration path. +The helper applies only the two declared field denials to the existing v1 role +snapshots, checks the persisted schema contract for unrelated drift, and then +writes `readonly.ser` and `restricted.ser`. Run +`FrozenGenericObjectRoleSchemaTest` afterwards. It is an offline source-generation +step, never a deployed-runtime patch. + ## Language and licensing The web console supplies user-facing translations. API fields, persisted diff --git a/code/framework/api-pub-sub-jetty/pom.xml b/code/framework/api-pub-sub-jetty/pom.xml index 127fdb75c9..6837ac40ed 100644 --- a/code/framework/api-pub-sub-jetty/pom.xml +++ b/code/framework/api-pub-sub-jetty/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/api-pub-sub/pom.xml b/code/framework/api-pub-sub/pom.xml index 65d17c4a4e..ff78e3e8b9 100644 --- a/code/framework/api-pub-sub/pom.xml +++ b/code/framework/api-pub-sub/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/api/pom.xml b/code/framework/api/pom.xml index 5a4b086333..14d87e0ba0 100644 --- a/code/framework/api/pom.xml +++ b/code/framework/api/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/archaius/pom.xml b/code/framework/archaius/pom.xml index ad5ac3620f..fd11c24d2a 100644 --- a/code/framework/archaius/pom.xml +++ b/code/framework/archaius/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-meta-parent - 0.183.328 + 0.183.329 ../../meta-parent/pom.xml diff --git a/code/framework/async/pom.xml b/code/framework/async/pom.xml index 78b00ea6a0..90f4f1af0f 100644 --- a/code/framework/async/pom.xml +++ b/code/framework/async/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/auditing/pom.xml b/code/framework/auditing/pom.xml index d6f1a698e7..794c5f0dde 100644 --- a/code/framework/auditing/pom.xml +++ b/code/framework/auditing/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/db-loader/pom.xml b/code/framework/db-loader/pom.xml index 61ca3a264b..4de2f090e9 100644 --- a/code/framework/db-loader/pom.xml +++ b/code/framework/db-loader/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/deferred/pom.xml b/code/framework/deferred/pom.xml index 9fbf16ad91..dd2a0fc3fc 100644 --- a/code/framework/deferred/pom.xml +++ b/code/framework/deferred/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/encryption/pom.xml b/code/framework/encryption/pom.xml index 1c4810719e..68fefe8e68 100644 --- a/code/framework/encryption/pom.xml +++ b/code/framework/encryption/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/engine/pom.xml b/code/framework/engine/pom.xml index 3bd8feaaae..cd18c3739c 100644 --- a/code/framework/engine/pom.xml +++ b/code/framework/engine/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/eventing/pom.xml b/code/framework/eventing/pom.xml index d0209a5dc4..3fc4bb586a 100644 --- a/code/framework/eventing/pom.xml +++ b/code/framework/eventing/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/events/pom.xml b/code/framework/events/pom.xml index baa5f84e59..c57596ebd3 100644 --- a/code/framework/events/pom.xml +++ b/code/framework/events/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/extension-spring/pom.xml b/code/framework/extension-spring/pom.xml index bb31759c52..b968d564c1 100644 --- a/code/framework/extension-spring/pom.xml +++ b/code/framework/extension-spring/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/extension/pom.xml b/code/framework/extension/pom.xml index b251c32eae..464eb12f5b 100644 --- a/code/framework/extension/pom.xml +++ b/code/framework/extension/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/java-server/pom.xml b/code/framework/java-server/pom.xml index 4f3ff8994b..bce836552e 100644 --- a/code/framework/java-server/pom.xml +++ b/code/framework/java-server/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/jmx/pom.xml b/code/framework/jmx/pom.xml index 0a247fa563..7478e65308 100644 --- a/code/framework/jmx/pom.xml +++ b/code/framework/jmx/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/jooq/pom.xml b/code/framework/jooq/pom.xml index 5741961d88..81ee08f8ae 100644 --- a/code/framework/jooq/pom.xml +++ b/code/framework/jooq/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/json/pom.xml b/code/framework/json/pom.xml index 67a1e0b6fe..abbc8b2632 100644 --- a/code/framework/json/pom.xml +++ b/code/framework/json/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/launcher/pom.xml b/code/framework/launcher/pom.xml index d430fe2309..e283623767 100644 --- a/code/framework/launcher/pom.xml +++ b/code/framework/launcher/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/lock/pom.xml b/code/framework/lock/pom.xml index a6fb652173..a234ba5612 100644 --- a/code/framework/lock/pom.xml +++ b/code/framework/lock/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/logback/pom.xml b/code/framework/logback/pom.xml index 9990b83527..65c2716acb 100644 --- a/code/framework/logback/pom.xml +++ b/code/framework/logback/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-meta-parent - 0.183.328 + 0.183.329 ../../meta-parent/pom.xml diff --git a/code/framework/managed-context/pom.xml b/code/framework/managed-context/pom.xml index f2373f1509..195c02bbd5 100644 --- a/code/framework/managed-context/pom.xml +++ b/code/framework/managed-context/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/metrics/pom.xml b/code/framework/metrics/pom.xml index 7cdd24fad8..d02ec9f844 100644 --- a/code/framework/metrics/pom.xml +++ b/code/framework/metrics/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/module/pom.xml b/code/framework/module/pom.xml index 4ddd3d4462..32423e40b3 100644 --- a/code/framework/module/pom.xml +++ b/code/framework/module/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/object/pom.xml b/code/framework/object/pom.xml index f6b6c68d2d..363ff179f2 100644 --- a/code/framework/object/pom.xml +++ b/code/framework/object/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/pool/pom.xml b/code/framework/pool/pom.xml index 27374c921d..6d7fb5d0c2 100644 --- a/code/framework/pool/pom.xml +++ b/code/framework/pool/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/resource-monitor/pom.xml b/code/framework/resource-monitor/pom.xml index d558ab39f2..3281175d30 100644 --- a/code/framework/resource-monitor/pom.xml +++ b/code/framework/resource-monitor/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/schema/pom.xml b/code/framework/schema/pom.xml index 6a67489b51..329af7ff57 100644 --- a/code/framework/schema/pom.xml +++ b/code/framework/schema/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/schema/src/test/java/io/cattle/platform/schema/processor/GenericObjectAuthOverlayTest.java b/code/framework/schema/src/test/java/io/cattle/platform/schema/processor/GenericObjectAuthOverlayTest.java new file mode 100644 index 0000000000..f2b8097bd3 --- /dev/null +++ b/code/framework/schema/src/test/java/io/cattle/platform/schema/processor/GenericObjectAuthOverlayTest.java @@ -0,0 +1,137 @@ +package io.cattle.platform.schema.processor; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.ibuildthecloud.gdapi.factory.SchemaFactory; +import io.github.ibuildthecloud.gdapi.id.IdFormatter; +import io.github.ibuildthecloud.gdapi.model.impl.FieldImpl; +import io.github.ibuildthecloud.gdapi.model.impl.SchemaImpl; +import io.github.ibuildthecloud.gdapi.model.impl.WrappedResource; +import java.lang.reflect.Proxy; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import org.junit.Test; + +/** The generic storage API must not bypass a plugin's low-role projection. */ +public class GenericObjectAuthOverlayTest { + private final Path schemas = Paths.get("../../../resources/content/schema"); + private final SchemaImpl parent = base("genericObject", null); + private final SchemaFactory factory = (SchemaFactory) Proxy.newProxyInstance( + SchemaFactory.class.getClassLoader(), new Class[] {SchemaFactory.class}, + (proxy, method, args) -> "getSchema".equals(method.getName()) + && "genericObject".equals(args[0]) ? parent : null); + private final IdFormatter ids = (IdFormatter) Proxy.newProxyInstance( + IdFormatter.class.getClassLoader(), new Class[] {IdFormatter.class}, + (proxy, method, args) -> "formatId".equals(method.getName()) ? args[1] : null); + + private SchemaImpl base(String type, String parentType) { + SchemaImpl schema = new SchemaImpl(); + schema.setId(type); + schema.setParent(parentType); + for (String name : Arrays.asList("name", "kind", "accountId", "state", "key", "resourceData", + "status", "image", "labels", "mode")) { + FieldImpl field = new FieldImpl(); + field.setName(name); + field.setType("resourceData".equals(name) ? "map[json]" : "string"); + schema.getResourceFields().put(name, field); + } + return schema; + } + + private void overlay(SchemaImpl schema, String... files) throws Exception { + AuthOverlayPostProcessor processor = new AuthOverlayPostProcessor(); + for (String file : files) { + Map document = new ObjectMapper().readValue(schemas.resolve(file).toFile(), Map.class); + processor.load((Map) document.get("authorize")); + } + processor.postProcess(schema, factory); + } + + private SchemaImpl projected(String type, String role) throws Exception { + SchemaImpl schema = base(type, "genericObject".equals(type) ? null : "genericObject"); + // The low-role processor is applied after the parent project factory, + // not merged into the parent's permissions table. + overlay(schema, "user/user-auth.json", "project/project-auth.json"); + if (role != null) { + overlay(schema, role + "/" + role + ".json"); + } + return schema; + } + + private Map response(SchemaImpl schema) { + Map fields = new LinkedHashMap(); + fields.put("name", "receiver-fixture"); + fields.put("kind", "webhookReceiver"); + fields.put("accountId", "project-fixture"); + fields.put("state", "active"); + fields.put("status", "complete"); + fields.put("image", "example.invalid/test:1"); + fields.put("labels", "test-label"); + fields.put("mode", "pull"); + fields.put("key", "test-capability"); + Map data = new LinkedHashMap(); + data.put("url", "https://example.invalid/endpoint?key=test-capability"); + data.put("config", "plugin-owned-configuration"); + fields.put("resourceData", data); + return new WrappedResource(ids, factory, schema, null, fields, null, "GET").getFields(); + } + + private void assertLowRole(SchemaImpl schema) { + assertFalse(schema.getResourceFields().containsKey("key")); + assertFalse(schema.getResourceFields().containsKey("resourceData")); + Map result = response(schema); + assertFalse(result.containsKey("key")); + assertFalse(result.containsKey("resourceData")); + assertEquals("receiver-fixture", result.get("name")); + assertEquals("webhookReceiver", result.get("kind")); + assertEquals("project-fixture", result.get("accountId")); + assertEquals("active", result.get("state")); + } + + @Test + public void lowRolesHideCapabilitiesAtTheSharedResponseBoundary() throws Exception { + for (String role : Arrays.asList("read-user", "restricted-user")) { + assertLowRole(projected("genericObject", role)); + } + } + + @Test + public void inheritedStorageFieldsCannotReintroduceCapabilities() throws Exception { + for (String role : Arrays.asList("read-user", "restricted-user")) { + assertLowRole(projected("pluginObject", role)); + assertLowRole(projected("register", role)); + } + } + + @Test + public void privilegedProjectClientsKeepPluginStorage() throws Exception { + SchemaImpl schema = projected("genericObject", null); + assertTrue(schema.getResourceFields().containsKey("key")); + assertTrue(schema.getResourceFields().containsKey("resourceData")); + Map result = response(schema); + assertEquals("test-capability", result.get("key")); + assertTrue(result.get("resourceData") instanceof Map); + } + + @Test + public void pullTaskStorageRemainsPrivateWithoutRemovingTypedStatus() throws Exception { + for (String role : Arrays.asList("read-user", "restricted-user")) { + SchemaImpl schema = projected("pullTask", role); + assertFalse(schema.getResourceFields().containsKey("key")); + assertFalse(schema.getResourceFields().containsKey("resourceData")); + assertTrue(schema.getResourceFields().containsKey("kind")); + assertTrue(schema.getResourceFields().containsKey("state")); + Map result = response(schema); + assertEquals("complete", result.get("status")); + assertEquals("example.invalid/test:1", result.get("image")); + assertEquals("test-label", result.get("labels")); + assertEquals("pull", result.get("mode")); + } + } +} diff --git a/code/framework/server/pom.xml b/code/framework/server/pom.xml index 62ee69471f..35d6f444d0 100644 --- a/code/framework/server/pom.xml +++ b/code/framework/server/pom.xml @@ -4,7 +4,7 @@ cattle-meta-parent io.cattle - 0.183.328 + 0.183.329 ../../meta-parent/pom.xml diff --git a/code/framework/spring/pom.xml b/code/framework/spring/pom.xml index 847d3e0c2d..b89ec08da8 100644 --- a/code/framework/spring/pom.xml +++ b/code/framework/spring/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/system-task/pom.xml b/code/framework/system-task/pom.xml index 211022d386..968064bec3 100644 --- a/code/framework/system-task/pom.xml +++ b/code/framework/system-task/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/token/pom.xml b/code/framework/token/pom.xml index 8854e7f7bf..58b4195033 100644 --- a/code/framework/token/pom.xml +++ b/code/framework/token/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/framework/utils/pom.xml b/code/framework/utils/pom.xml index 6f2da549cb..1310372727 100644 --- a/code/framework/utils/pom.xml +++ b/code/framework/utils/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/agent-instance/pom.xml b/code/iaas/agent-instance/pom.xml index 4352169450..d11713003b 100644 --- a/code/iaas/agent-instance/pom.xml +++ b/code/iaas/agent-instance/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/agent-server/pom.xml b/code/iaas/agent-server/pom.xml index cb4533443e..89a90ed9c2 100644 --- a/code/iaas/agent-server/pom.xml +++ b/code/iaas/agent-server/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/agent/pom.xml b/code/iaas/agent/pom.xml index 82e3570b68..e98234dbd5 100644 --- a/code/iaas/agent/pom.xml +++ b/code/iaas/agent/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/allocator/pom.xml b/code/iaas/allocator/pom.xml index b1554f586d..9e495069af 100644 --- a/code/iaas/allocator/pom.xml +++ b/code/iaas/allocator/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/api-logic/pom.xml b/code/iaas/api-logic/pom.xml index 2e8eb62e41..0f4bf4634c 100644 --- a/code/iaas/api-logic/pom.xml +++ b/code/iaas/api-logic/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/archaius-management/pom.xml b/code/iaas/archaius-management/pom.xml index 4e85f1bb84..bc93c65417 100644 --- a/code/iaas/archaius-management/pom.xml +++ b/code/iaas/archaius-management/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/auth-logic/pom.xml b/code/iaas/auth-logic/pom.xml index 9e541cb00f..b94cfce7f6 100644 --- a/code/iaas/auth-logic/pom.xml +++ b/code/iaas/auth-logic/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/bootstrap/pom.xml b/code/iaas/bootstrap/pom.xml index 1bd2e65bf9..fb5671c925 100644 --- a/code/iaas/bootstrap/pom.xml +++ b/code/iaas/bootstrap/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/config-item/api/pom.xml b/code/iaas/config-item/api/pom.xml index ef45191dcc..f3ad97e8d0 100644 --- a/code/iaas/config-item/api/pom.xml +++ b/code/iaas/config-item/api/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../../parent/pom.xml diff --git a/code/iaas/config-item/common/pom.xml b/code/iaas/config-item/common/pom.xml index 297a1631dc..169fd7d1c2 100644 --- a/code/iaas/config-item/common/pom.xml +++ b/code/iaas/config-item/common/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../../parent/pom.xml diff --git a/code/iaas/config-item/server/pom.xml b/code/iaas/config-item/server/pom.xml index b22ba12d8b..99370dc09a 100644 --- a/code/iaas/config-item/server/pom.xml +++ b/code/iaas/config-item/server/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../../parent/pom.xml diff --git a/code/iaas/engine-jooq/pom.xml b/code/iaas/engine-jooq/pom.xml index 3bcd1ac9e1..69ce2ba412 100644 --- a/code/iaas/engine-jooq/pom.xml +++ b/code/iaas/engine-jooq/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/events/pom.xml b/code/iaas/events/pom.xml index c341f18a31..1e33aba6f4 100644 --- a/code/iaas/events/pom.xml +++ b/code/iaas/events/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/external-handler/pom.xml b/code/iaas/external-handler/pom.xml index f850e3061a..98ac47e63a 100644 --- a/code/iaas/external-handler/pom.xml +++ b/code/iaas/external-handler/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/ha/pom.xml b/code/iaas/ha/pom.xml index f65cc3a2fa..cf84982670 100644 --- a/code/iaas/ha/pom.xml +++ b/code/iaas/ha/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/healthcheck/pom.xml b/code/iaas/healthcheck/pom.xml index a30c264090..708dc6fa1c 100644 --- a/code/iaas/healthcheck/pom.xml +++ b/code/iaas/healthcheck/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/labels/pom.xml b/code/iaas/labels/pom.xml index d1886d20a5..7355825de8 100644 --- a/code/iaas/labels/pom.xml +++ b/code/iaas/labels/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/logic-common/pom.xml b/code/iaas/logic-common/pom.xml index f48905a6e4..593f912b16 100644 --- a/code/iaas/logic-common/pom.xml +++ b/code/iaas/logic-common/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/logic/pom.xml b/code/iaas/logic/pom.xml index cbf41e034f..300646ddef 100644 --- a/code/iaas/logic/pom.xml +++ b/code/iaas/logic/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/metadata/pom.xml b/code/iaas/metadata/pom.xml index ea96c2f2a7..040876d02a 100644 --- a/code/iaas/metadata/pom.xml +++ b/code/iaas/metadata/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/model/pom.xml b/code/iaas/model/pom.xml index 68407581a7..3bd25d0168 100644 --- a/code/iaas/model/pom.xml +++ b/code/iaas/model/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/resource-pool/pom.xml b/code/iaas/resource-pool/pom.xml index 5c7e7cf91d..f0629558b3 100644 --- a/code/iaas/resource-pool/pom.xml +++ b/code/iaas/resource-pool/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/service-discovery/api/pom.xml b/code/iaas/service-discovery/api/pom.xml index e5a2799e51..b47dba16ee 100644 --- a/code/iaas/service-discovery/api/pom.xml +++ b/code/iaas/service-discovery/api/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../../parent/pom.xml diff --git a/code/iaas/service-discovery/server/pom.xml b/code/iaas/service-discovery/server/pom.xml index c3b012215c..104fe4d760 100644 --- a/code/iaas/service-discovery/server/pom.xml +++ b/code/iaas/service-discovery/server/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../../parent/pom.xml diff --git a/code/iaas/ssh-common/pom.xml b/code/iaas/ssh-common/pom.xml index 5cad182a86..b3522603fe 100644 --- a/code/iaas/ssh-common/pom.xml +++ b/code/iaas/ssh-common/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/storage-service/pom.xml b/code/iaas/storage-service/pom.xml index b3f2b33fbc..dbd8d5f162 100644 --- a/code/iaas/storage-service/pom.xml +++ b/code/iaas/storage-service/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/iaas/task-jooq/pom.xml b/code/iaas/task-jooq/pom.xml index 38e74054df..29328a3bf7 100644 --- a/code/iaas/task-jooq/pom.xml +++ b/code/iaas/task-jooq/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/implementation/activity-log/pom.xml b/code/implementation/activity-log/pom.xml index 257a239a75..dac415e424 100644 --- a/code/implementation/activity-log/pom.xml +++ b/code/implementation/activity-log/pom.xml @@ -5,7 +5,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/implementation/agent-instance-impl/pom.xml b/code/implementation/agent-instance-impl/pom.xml index df51b05e74..1a1b15a282 100644 --- a/code/implementation/agent-instance-impl/pom.xml +++ b/code/implementation/agent-instance-impl/pom.xml @@ -3,7 +3,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml cattle-agent-instance-impl diff --git a/code/implementation/docker/api/pom.xml b/code/implementation/docker/api/pom.xml index c852088b8a..36ed44941b 100644 --- a/code/implementation/docker/api/pom.xml +++ b/code/implementation/docker/api/pom.xml @@ -5,7 +5,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../../parent/pom.xml diff --git a/code/implementation/docker/common/pom.xml b/code/implementation/docker/common/pom.xml index 20e2496010..0c763f3b6e 100644 --- a/code/implementation/docker/common/pom.xml +++ b/code/implementation/docker/common/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../../parent/pom.xml diff --git a/code/implementation/docker/compute/pom.xml b/code/implementation/docker/compute/pom.xml index 005c918ce0..c166000654 100644 --- a/code/implementation/docker/compute/pom.xml +++ b/code/implementation/docker/compute/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../../parent/pom.xml diff --git a/code/implementation/docker/machine/pom.xml b/code/implementation/docker/machine/pom.xml index fff87ad881..26eb011662 100644 --- a/code/implementation/docker/machine/pom.xml +++ b/code/implementation/docker/machine/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../../parent/pom.xml diff --git a/code/implementation/docker/storage/pom.xml b/code/implementation/docker/storage/pom.xml index 4f24761f2f..a0f4805b8d 100644 --- a/code/implementation/docker/storage/pom.xml +++ b/code/implementation/docker/storage/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../../parent/pom.xml @@ -21,12 +21,12 @@ io.cattle cattle-docker-common - 0.183.328 + 0.183.329 io.cattle cattle-iaas-allocator - 0.183.328 + 0.183.329 diff --git a/code/implementation/extension-api/pom.xml b/code/implementation/extension-api/pom.xml index b052faffe6..7126313e64 100644 --- a/code/implementation/extension-api/pom.xml +++ b/code/implementation/extension-api/pom.xml @@ -3,7 +3,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml cattle-extension-api diff --git a/code/implementation/hazelcast/common/pom.xml b/code/implementation/hazelcast/common/pom.xml index facdafddd6..0558dd5945 100644 --- a/code/implementation/hazelcast/common/pom.xml +++ b/code/implementation/hazelcast/common/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../../parent/pom.xml diff --git a/code/implementation/hazelcast/eventing/pom.xml b/code/implementation/hazelcast/eventing/pom.xml index 07ba98745b..6ac3b82944 100644 --- a/code/implementation/hazelcast/eventing/pom.xml +++ b/code/implementation/hazelcast/eventing/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../../parent/pom.xml diff --git a/code/implementation/hazelcast/lock/pom.xml b/code/implementation/hazelcast/lock/pom.xml index 5700f8c2ba..1cedc4e20a 100644 --- a/code/implementation/hazelcast/lock/pom.xml +++ b/code/implementation/hazelcast/lock/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../../parent/pom.xml diff --git a/code/implementation/host-api/pom.xml b/code/implementation/host-api/pom.xml index 07be1b8caa..6842b2025e 100644 --- a/code/implementation/host-api/pom.xml +++ b/code/implementation/host-api/pom.xml @@ -5,7 +5,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/implementation/host-stats/pom.xml b/code/implementation/host-stats/pom.xml index d22f2301ee..09620e73c4 100644 --- a/code/implementation/host-stats/pom.xml +++ b/code/implementation/host-stats/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/implementation/register/pom.xml b/code/implementation/register/pom.xml index 889eddf3c6..b190295d45 100644 --- a/code/implementation/register/pom.xml +++ b/code/implementation/register/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/implementation/sample-setup/pom.xml b/code/implementation/sample-setup/pom.xml index 11c11d8292..14767db3ff 100644 --- a/code/implementation/sample-setup/pom.xml +++ b/code/implementation/sample-setup/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/implementation/settings-api/pom.xml b/code/implementation/settings-api/pom.xml index 8a10b9d2c7..b2ed51b96d 100644 --- a/code/implementation/settings-api/pom.xml +++ b/code/implementation/settings-api/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/implementation/simulator/agent-connection/pom.xml b/code/implementation/simulator/agent-connection/pom.xml index 26fff2feaf..76c8fddaa2 100644 --- a/code/implementation/simulator/agent-connection/pom.xml +++ b/code/implementation/simulator/agent-connection/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../../parent/pom.xml diff --git a/code/implementation/simulator/storage/pom.xml b/code/implementation/simulator/storage/pom.xml index db80e23608..5472f84df3 100644 --- a/code/implementation/simulator/storage/pom.xml +++ b/code/implementation/simulator/storage/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../../parent/pom.xml diff --git a/code/implementation/system-stack/pom.xml b/code/implementation/system-stack/pom.xml index 6b08b149d6..ac6fef404b 100644 --- a/code/implementation/system-stack/pom.xml +++ b/code/implementation/system-stack/pom.xml @@ -5,7 +5,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/implementation/vm/pom.xml b/code/implementation/vm/pom.xml index 6c26be22e1..67e0682622 100644 --- a/code/implementation/vm/pom.xml +++ b/code/implementation/vm/pom.xml @@ -3,7 +3,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml cattle-vm diff --git a/code/meta-parent/pom.xml b/code/meta-parent/pom.xml index 38669d5791..ad194a7bb1 100644 --- a/code/meta-parent/pom.xml +++ b/code/meta-parent/pom.xml @@ -9,7 +9,7 @@ 4.0.0 io.cattle cattle-meta-parent - 0.183.328 + 0.183.329 pom PastureStack Orchestration Engine Compatibility orchestration engine for the PastureStack server. diff --git a/code/packaging/app-config/pom.xml b/code/packaging/app-config/pom.xml index 7108c1dd37..6e9c1e9c76 100644 --- a/code/packaging/app-config/pom.xml +++ b/code/packaging/app-config/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml jar diff --git a/code/packaging/app/pom.xml b/code/packaging/app/pom.xml index 40e24b947f..5bd8581098 100644 --- a/code/packaging/app/pom.xml +++ b/code/packaging/app/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml war diff --git a/code/packaging/bundle/pom.xml b/code/packaging/bundle/pom.xml index 6ce9d9279f..7de63641a0 100644 --- a/code/packaging/bundle/pom.xml +++ b/code/packaging/bundle/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/packaging/dev/pom.xml b/code/packaging/dev/pom.xml index f7828b12b9..26d2bf66f7 100644 --- a/code/packaging/dev/pom.xml +++ b/code/packaging/dev/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/packaging/meta/pom.xml b/code/packaging/meta/pom.xml index e4fe63274b..834467b112 100644 --- a/code/packaging/meta/pom.xml +++ b/code/packaging/meta/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../../parent/pom.xml diff --git a/code/parent/pom.xml b/code/parent/pom.xml index 52a25b01c7..e0f7426f2e 100644 --- a/code/parent/pom.xml +++ b/code/parent/pom.xml @@ -5,7 +5,7 @@ io.cattle cattle-meta-parent ../meta-parent/pom.xml - 0.183.328 + 0.183.329 pom diff --git a/docs/releases/orchestration-engine-0.183.329.md b/docs/releases/orchestration-engine-0.183.329.md new file mode 100644 index 0000000000..ed930e8055 --- /dev/null +++ b/docs/releases/orchestration-engine-0.183.329.md @@ -0,0 +1,51 @@ +# Orchestration Engine 0.183.329 + +## Behavior + +Readonly and restricted project roles could read a Receiver's stored capability +through GenericObject even though the Receiver API omitted its execution URL. +The QA reproduction on Server 1.6.497 / Engine 0.183.328 confirmed an exact v1 +GET returned a nonempty key and a URL matching the owner's capability hash. +The execution endpoint was not called. + +The fix uses the existing authorization boundary: the two low-role schema +overlays deny `genericObject.key` and `genericObject.resourceData`. This also +applies to inherited storage fields. It does not hardcode a plugin kind, parse +URLs, change hook execution, or introduce another secret-storage system. +The frozen v1 readonly/restricted schemas receive the same field denials. + +Resource metadata stays readable. Receiver configuration remains available +through its typed plugin API, with execution URLs still hidden for low roles. +Owner/member/service clients retain plugin storage and normal Receiver writes. +Consumers that directly read opaque GenericObject data with a low-role token +must use the authorized typed API instead. Project/identity authorization, +MFA, cookie/session ownership, and unrelated write policy are unchanged. + +## Focused evidence + +- Before the fix: 4 overlay tests, 2 expected failures, 0 errors/skips. +- After the fix: all 4 overlay/response-projection tests and 2 actual frozen-v1 + tests pass with Maven 3.9.16 / JDK 25.0.4. +- The offline producer checks persisted schema equality: only key/resourceData + are removed from GenericObject and its inherited Register schema in each + low-role snapshot; unrelated schema/method/action/filter/field data is retained. +- Owner/member/project/service frozen schemas retain both fields. +- The package gate verifies the reviewed JSON denials and exact frozen role + bytes inside the actual WAR, preventing a JSON-only or stale-v1 publication. +- The offline snapshot producer rejects path arguments and unreviewed input + hashes, and uses a bounded exact-class deserialization filter. The filter + accepts both current role snapshots and rejects an unexpected graph before + its deserialization callback executes. An initial CodeQL candidate detected + the former command-line-path/unfiltered producer; that failed run is retained, + not treated as a passing gate. + +## Release status and recovery + +The source is prepared for the normal immutable component build/publication. +Artifact hash, source revision, CI evidence, Server consumption, and real +v1/v2-beta role/browser results will be recorded after those gates complete. +No full permission-matrix acceptance is implied by these focused tests. + +There is no database migration and no HAProxy/runtime patch. A rollback to the +previous image restores the previous schema visibility, including this +capability-read weakness; preserve existing configuration and named volumes. diff --git a/pom.xml b/pom.xml index f1e2b71c3e..3c8d58a8ea 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ io.cattle cattle-parent - 0.183.328 + 0.183.329 code/parent/pom.xml cattle diff --git a/resources/content/schema/read-user/read-user.json b/resources/content/schema/read-user/read-user.json index 4afc824c11..fa178b0a5e 100644 --- a/resources/content/schema/read-user/read-user.json +++ b/resources/content/schema/read-user/read-user.json @@ -3,6 +3,8 @@ ".*\\.resourceActions\\..*": "", ".*\\.uuid": "r", "registrationToken": "", + "genericObject.key": "", + "genericObject.resourceData": "", "container.resourceActions.logs": "c", "instance.environment": "", @@ -12,4 +14,4 @@ "end" : "" } -} \ No newline at end of file +} diff --git a/resources/content/schema/restricted-user/restricted-user.json b/resources/content/schema/restricted-user/restricted-user.json index ce529c175a..340ca92aaa 100644 --- a/resources/content/schema/restricted-user/restricted-user.json +++ b/resources/content/schema/restricted-user/restricted-user.json @@ -5,6 +5,8 @@ "physicalHost": "r", "physicalHost.resourceActions\\..*": "", "registrationToken" : "", + "genericObject.key": "", + "genericObject.resourceData": "", "end" : "" } diff --git a/resources/content/schema/v1/readonly.ser b/resources/content/schema/v1/readonly.ser index 7862d61795..7ee8b50513 100644 Binary files a/resources/content/schema/v1/readonly.ser and b/resources/content/schema/v1/readonly.ser differ diff --git a/resources/content/schema/v1/restricted.ser b/resources/content/schema/v1/restricted.ser index 6ec35d367f..d1328b1c08 100644 Binary files a/resources/content/schema/v1/restricted.ser and b/resources/content/schema/v1/restricted.ser differ diff --git a/resources/pom.xml b/resources/pom.xml index 2cafb59ae7..f6036f290b 100644 --- a/resources/pom.xml +++ b/resources/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.328 + 0.183.329 ../code/parent/pom.xml diff --git a/resources/src/test/java/io/cattle/platform/resources/FrozenGenericObjectRoleSchemaTest.java b/resources/src/test/java/io/cattle/platform/resources/FrozenGenericObjectRoleSchemaTest.java new file mode 100644 index 0000000000..2205937779 --- /dev/null +++ b/resources/src/test/java/io/cattle/platform/resources/FrozenGenericObjectRoleSchemaTest.java @@ -0,0 +1,62 @@ +package io.cattle.platform.resources; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import io.github.ibuildthecloud.gdapi.model.Schema; +import java.io.FileInputStream; +import java.io.ObjectInputStream; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.Test; + +/** v1 reads these files directly; JSON-only authorization fixes are insufficient. */ +public class FrozenGenericObjectRoleSchemaTest { + private Map read(String role) throws Exception { + Map result = new LinkedHashMap(); + try (ObjectInputStream input = new ObjectInputStream(new FileInputStream( + Paths.get("content", "schema", "v1", role + ".ser").toFile()))) { + for (Object item : (List) input.readObject()) { + Schema schema = Schema.class.cast(item); + result.put(schema.getId(), schema); + } + } + return result; + } + + @Test + public void lowRoleFrozenSchemasCannotRevealGenericStorageCapabilities() throws Exception { + for (String role : Arrays.asList("readonly", "restricted")) { + Map schemas = read(role); + assertNotNull(role, schemas.get("genericObject")); + for (Schema schema : schemas.values()) { + Schema current = schema; + while (current != null && !"genericObject".equals(current.getId())) { + current = schemas.get(current.getParent()); + } + if (current != null) { + assertFalse(role + "/" + schema.getId(), schema.getResourceFields().containsKey("key")); + assertFalse(role + "/" + schema.getId(), schema.getResourceFields().containsKey("resourceData")); + } + } + Schema generic = schemas.get("genericObject"); + for (String field : Arrays.asList("name", "kind", "accountId", "state")) { + assertTrue(role + "/" + field, generic.getResourceFields().containsKey(field)); + } + } + } + + @Test + public void privilegedProjectSchemasRetainPluginStorage() throws Exception { + for (String role : Arrays.asList("owner", "member", "project", "service")) { + Schema schema = read(role).get("genericObject"); + assertNotNull(role, schema); + assertTrue(role, schema.getResourceFields().containsKey("key")); + assertTrue(role, schema.getResourceFields().containsKey("resourceData")); + } + } +} diff --git a/scripts/build b/scripts/build index aee980f2b6..9f6de53a79 100755 --- a/scripts/build +++ b/scripts/build @@ -16,7 +16,7 @@ fi SOURCE_REVISION=${SOURCE_REVISION:-$(git rev-parse HEAD)} SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-$(git show -s --format=%ct HEAD)} -ENGINE_VERSION=${ENGINE_VERSION:-0.183.328} +ENGINE_VERSION=${ENGINE_VERSION:-0.183.329} case "$SOURCE_REVISION" in ''|*[!0-9a-f]*) diff --git a/scripts/check-pasturestack-source b/scripts/check-pasturestack-source index bbb8ba4125..9721dee535 100755 --- a/scripts/check-pasturestack-source +++ b/scripts/check-pasturestack-source @@ -51,7 +51,7 @@ fi project_version=$(sed -n 's/^[[:space:]]*\([^<]*\)<\/version>[[:space:]]*$/\1/p' code/meta-parent/pom.xml | head -n 1) [[ "$project_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || fail non_numeric_project_version -require_line code/meta-parent/pom.xml ' 0.183.328' +require_line code/meta-parent/pom.xml ' 0.183.329' require_line code/meta-parent/pom.xml ' 2.3.35' require_line "$iaas_api_defaults" 'auth.service.external.id.types=github_user,github_org,github_team,shibboleth_user,shibboleth_group,ldap_user,ldap_group,oidc_user,oidc_group' require_line code/meta-parent/pom.xml ' https://github.com/PastureStack/orchestration-engine' @@ -416,4 +416,4 @@ require_line README.md 'modernize the Rancher 1.6 ecosystem. It is not affiliate require_line README.md 'by Rancher Labs or SUSE.' require_line ORIGIN.md '- Preserved upstream boundary: `82d154a53f4089fecfb9f320caad826bb4f6055f`' -printf 'PASTURESTACK_SOURCE_GATE_OK version=0.183.328 runtime_sources=github_release images=digest_pinned ubuntu=26.04 ubuntu_snapshot=20260826T000000Z jdk=25.0.4 maven=3.9.16 patched_hazelcast=5.7.5 docker_cli=29.7.2 docker_host_29_8_0=exact credential_secret_capacity=mediumtext port_preflight=authoritative volume_preflight=runtime_resolution_aligned volume_preflight_project_schema=authorized volume_preflight_type_set=registered v1_hardware_schema=container-and-launchConfig network_driver_rollback=launch-config-restored stack_driver_rollback=child-launch-config-restored auth_token_session_binding=authorized-create-only auth_token_transport=bare-or-bearer-normalized auth_token_frozen_v1_schema=base-superadmin-token oidc_external_types=validated-before-mutation-owned-stable-account oidc_account_activation=sync-before-mfa oidc_v1_project_member_schema=core-options-merged-scoped oidc_identity_link_owner=explicit-and-verified legacy_token_link_repair=exact-match-only oidc_required_local_recovery=active-admin-only default_project=shared-idempotent-role-preserving,atomic-identity-set oidc_restricted_project_membership=stable-account-aware-required-allowlist-only mfa_policy_confirmation=actor-purpose-digest-single-use auth_config_proxy_identity=caller-platform-credential project_member_collection_acl=requested-project-checked-before-load project_template_v1_public=readonly-frozen-field-merged network_purge=retry-removing dev_artifact=forbidden\n' +printf 'PASTURESTACK_SOURCE_GATE_OK version=0.183.329 runtime_sources=github_release images=digest_pinned ubuntu=26.04 ubuntu_snapshot=20260826T000000Z jdk=25.0.4 maven=3.9.16 patched_hazelcast=5.7.5 docker_cli=29.7.2 docker_host_29_8_0=exact credential_secret_capacity=mediumtext port_preflight=authoritative volume_preflight=runtime_resolution_aligned volume_preflight_project_schema=authorized volume_preflight_type_set=registered v1_hardware_schema=container-and-launchConfig network_driver_rollback=launch-config-restored stack_driver_rollback=child-launch-config-restored auth_token_session_binding=authorized-create-only auth_token_transport=bare-or-bearer-normalized auth_token_frozen_v1_schema=base-superadmin-token oidc_external_types=validated-before-mutation-owned-stable-account oidc_account_activation=sync-before-mfa oidc_v1_project_member_schema=core-options-merged-scoped oidc_identity_link_owner=explicit-and-verified legacy_token_link_repair=exact-match-only oidc_required_local_recovery=active-admin-only default_project=shared-idempotent-role-preserving,atomic-identity-set oidc_restricted_project_membership=stable-account-aware-required-allowlist-only mfa_policy_confirmation=actor-purpose-digest-single-use auth_config_proxy_identity=caller-platform-credential project_member_collection_acl=requested-project-checked-before-load project_template_v1_public=readonly-frozen-field-merged network_purge=retry-removing dev_artifact=forbidden\n' diff --git a/scripts/check-release-artifact b/scripts/check-release-artifact index 05de760b45..f7e5fd72b9 100755 --- a/scripts/check-release-artifact +++ b/scripts/check-release-artifact @@ -4,7 +4,7 @@ set -euo pipefail cd "$(dirname "$0")/.." artifact=${1:-dist/artifacts/cattle.jar} -expected_version=${EXPECTED_ENGINE_VERSION:-0.183.328} +expected_version=${EXPECTED_ENGINE_VERSION:-0.183.329} test -f "$artifact" artifact=$(realpath "$artifact") @@ -172,6 +172,18 @@ for frozen_token_schema in base.ser superadmin.ser token.ser; do unzip -p "$workdir/$resources_entry" "schema/v1/$frozen_token_schema" | grep -aF 'clientSessionId' >/dev/null done +for role in read-user restricted-user; do + for field in key resourceData; do + unzip -p "$workdir/$resources_entry" "schema/$role/$role.json" | + grep -F "\"genericObject.$field\": \"\"" >/dev/null + done +done +# v1 bypasses the JSON overlay. Require the exact reviewed frozen role artifacts, +# not a string marker that could also occur in another schema in the same file. +for role in readonly restricted; do + unzip -p "$workdir/$resources_entry" "schema/v1/$role.ser" > "$workdir/$role.ser" + cmp "resources/content/schema/v1/$role.ser" "$workdir/$role.ser" +done unzip -p "$workdir/$resources_entry" cattle-global.properties | grep -Fx 'auth.service.external.id.types=github_user,github_org,github_team,shibboleth_user,shibboleth_group,ldap_user,ldap_group,oidc_user,oidc_group' >/dev/null diff --git a/scripts/java/UpdateFrozenGenericObjectSchemas.java b/scripts/java/UpdateFrozenGenericObjectSchemas.java new file mode 100644 index 0000000000..456c59ef0a --- /dev/null +++ b/scripts/java/UpdateFrozenGenericObjectSchemas.java @@ -0,0 +1,208 @@ +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.cattle.platform.schema.processor.AuthOverlayPostProcessor; +import io.github.ibuildthecloud.gdapi.factory.SchemaFactory; +import io.github.ibuildthecloud.gdapi.factory.impl.SchemaPostProcessor; +import io.github.ibuildthecloud.gdapi.factory.impl.SubSchemaFactory; +import io.github.ibuildthecloud.gdapi.model.Schema; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InvalidClassException; +import java.io.ObjectInputFilter; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Proxy; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** Offline source migration: preserve v1 snapshots, apply only shipped low-role rules. */ +class UpdateFrozenGenericObjectSchemas { + // This one-time migration accepts only the reviewed 0.183.328 snapshots. + // Neither a caller-provided path nor an arbitrary serialized graph is input. + private static final ObjectInputFilter SCHEMA_FILTER = ObjectInputFilter.Config.createFilter( + "maxdepth=80;maxrefs=100000;maxbytes=2097152;maxarray=100000;" + + "io.github.ibuildthecloud.gdapi.model.impl.SchemaImpl;" + + "io.github.ibuildthecloud.gdapi.model.impl.FieldImpl;" + + "io.github.ibuildthecloud.gdapi.model.Action;" + + "io.github.ibuildthecloud.gdapi.model.Filter;" + + "io.github.ibuildthecloud.gdapi.model.FieldType;" + + "java.lang.String;java.lang.Boolean;java.lang.Long;java.lang.Number;" + + "java.lang.Enum;java.lang.Object;java.util.ArrayList;java.util.HashMap;" + + "java.util.LinkedHashMap;java.util.Map$Entry;!*"); + + private static ObjectInputStream schemaInput(byte[] bytes) throws Exception { + ObjectInputStream input = new ObjectInputStream(new ByteArrayInputStream(bytes)); + input.setObjectInputFilter(SCHEMA_FILTER); + return input; + } + + private static byte[] reviewedSnapshot(Path path, String role) throws Exception { + if (!Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS) || Files.size(path) > 2097152) { + throw new IllegalArgumentException("Expected regular source snapshot: " + role); + } + byte[] bytes = Files.readAllBytes(path); + String expected = "readonly".equals(role) + ? "9be2084fabdb9f70e664009d98ca4cbfdc456645c3ad4faf683c5a8af5f83e88" + : "2394998f427a3868d0053c445c7f2b1c690fae4e9666174d5ee4ee938f58e96d"; + if (!java.util.HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(bytes)).equals(expected)) { + throw new IllegalArgumentException("Unreviewed or already migrated source snapshot: " + role); + } + return bytes; + } + + private static class UnexpectedGraph implements Serializable { + private static final long serialVersionUID = 1L; + static boolean executed; + + private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { + executed = true; + input.defaultReadObject(); + } + } + + private static void checkFilter() throws Exception { + for (String role : Arrays.asList("readonly", "restricted")) { + byte[] bytes = Files.readAllBytes(Paths.get("resources/content/schema/v1/" + role + ".ser")); + try (ObjectInputStream input = schemaInput(bytes)) { + List schemas = (List) input.readObject(); + if (schemas.isEmpty()) { + throw new AssertionError("Empty schema snapshot"); + } + for (Object schema : schemas) { + Schema.class.cast(schema); + } + } + } + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (ObjectOutputStream output = new ObjectOutputStream(bytes)) { + output.writeObject(new UnexpectedGraph()); + } + try (ObjectInputStream input = schemaInput(bytes.toByteArray())) { + input.readObject(); + throw new AssertionError("Unexpected serialized class accepted"); + } catch (InvalidClassException expected) { + if (UnexpectedGraph.executed) { + throw new AssertionError("Rejected graph executed its callback"); + } + } + System.out.println("FROZEN_SCHEMA_FILTER_OK known_roles=2 unexpected_graph_rejected=1 callback_executed=0"); + } + + private static class Overlay extends AuthOverlayPostProcessor { + void document(Map doc) { + Map declared = (Map) doc.get("authorize"); + Map changed = new LinkedHashMap(); + for (String name : Arrays.asList("genericObject.key", "genericObject.resourceData")) { + if (!"".equals(declared.get(name))) { + throw new IllegalStateException("Expected explicit field denial: " + name); + } + changed.put(name, ""); + } + load(changed); + } + } + + public static void main(String[] args) throws Exception { + if (args.length == 1 && "--check-filter".equals(args[0])) { + checkFilter(); + return; + } + if (args.length != 0) { + throw new IllegalArgumentException("Run from repository root; no path arguments accepted"); + } + Path root = Paths.get(".").toRealPath(); + ObjectMapper mapper = new ObjectMapper(); + Map outputs = new LinkedHashMap(); + for (String role : Arrays.asList("readonly", "restricted")) { + String overlay = "readonly".equals(role) ? "read-user" : "restricted-user"; + Path path = root.resolve("resources/content/schema/v1/" + role + ".ser"); + List original = new ArrayList(); + Map index = new LinkedHashMap(); + try (ObjectInputStream input = schemaInput(reviewedSnapshot(path, role))) { + for (Object item : (List) input.readObject()) { + Schema schema = Schema.class.cast(item); + original.add(schema); + index.put(schema.getId(), schema); + } + } + SchemaFactory parent = (SchemaFactory) Proxy.newProxyInstance( + SchemaFactory.class.getClassLoader(), new Class[] {SchemaFactory.class}, + (proxy, method, values) -> { + if ("listSchemas".equals(method.getName())) { + return original; + } + return "getSchema".equals(method.getName()) ? index.get(values[0]) : null; + }); + Overlay auth = new Overlay(); + auth.document(mapper.readValue(root.resolve("resources/content/schema/" + overlay + "/" + + overlay + ".json").toFile(), Map.class)); + SubSchemaFactory factory = new SubSchemaFactory(); + factory.setId(role); + factory.setSchemaFactory(parent); + factory.setPostProcessors(Arrays.asList(auth)); + factory.init(); + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (ObjectOutputStream output = new ObjectOutputStream(bytes)) { + output.writeObject(factory.listSchemas()); + } + // Compare the persisted contract, not non-serializable ResourceImpl + // constructor state (type/baseType) on the in-memory factory copy. + List result = new ArrayList(); + try (ObjectInputStream input = schemaInput(bytes.toByteArray())) { + for (Object item : (List) input.readObject()) { + result.add(Schema.class.cast(item)); + } + } + if (result.size() != original.size()) { + throw new IllegalStateException("Unexpected schema removal: " + role); + } + int changed = 0; + for (Schema schema : result) { + Schema before = index.get(schema.getId()); + JsonNode expected = mapper.valueToTree(before); + Schema ancestor = before; + while (ancestor != null && !"genericObject".equals(ancestor.getId())) { + ancestor = index.get(ancestor.getParent()); + } + if (ancestor != null) { + ObjectNode fields = (ObjectNode) expected.get("resourceFields"); + if (fields.remove("key") != null | fields.remove("resourceData") != null) { + changed++; + } + } + JsonNode actual = mapper.valueToTree(schema); + if (!expected.equals(actual)) { + expected.fieldNames().forEachRemaining(name -> { + if (!expected.get(name).equals(actual.get(name))) { + System.err.println("Changed schema property: " + name); + } + }); + throw new IllegalStateException("Unrelated frozen schema drift: " + role + "/" + schema.getId()); + } + } + if (changed == 0) { + throw new IllegalStateException("Already migrated or missing generic storage: " + role); + } + outputs.put(path, bytes.toByteArray()); + System.out.println(role + ": verified field-only changes in " + changed + " schemas"); + } + // Validate both roles before writing either source artifact. + for (Map.Entry entry : outputs.entrySet()) { + try (FileOutputStream output = new FileOutputStream(entry.getKey().toFile())) { + output.write(entry.getValue()); + } + } + } +}