diff --git a/.github/workflows/security-release-gate.yml b/.github/workflows/security-release-gate.yml index 3d909f1603..1eb42bcd48 100644 --- a/.github/workflows/security-release-gate.yml +++ b/.github/workflows/security-release-gate.yml @@ -57,8 +57,30 @@ jobs: import xml.etree.ElementTree as ET required = { + 'io.github.ibuildthecloud.gdapi.model.impl.CreateOnlyWrappedResourceTest': { + 'trueCreationRetainsTheFirstSecretInBothApiVersions', + 'postActionsCannotRevealCreateOnlyValues', + 'readUpdateDeleteAndUnknownMethodsStillRedact', + 'rolesWithoutCreateOnlyRestrictionKeepTheirExistingReadContract', + 'methodOnlyLegacyConstructorsFailClosedInsteadOfGuessingCreation', + 'priorityFieldsCannotBypassTheCreateOnlyBoundary', + }, + 'io.github.ibuildthecloud.gdapi.model.impl.CreateOnlyCallerTest': { + 'managerConstructResourceUsesTheRequestActionNotOnlyPostMethod', + 'responseWriterCreateResourceUsesTheActualContextRequest', + }, + 'io.cattle.platform.api.utils.ApiUtilsCreateOnlyTest': { + 'trueCreateAllowsBeanAndAdditionalFieldValuesInBothRoots', + 'postActionsRedactBothValueSourcesWithoutRedactingUnflaggedNames', + 'nullRequestFailsClosedForBothValueSources', + 'readUpdateAndDeleteCannotUseTheCreateException', + }, + 'io.github.ibuildthecloud.gdapi.util.RequestUtilsTest': { + 'creationRequiresPostAndAbsentActionNotAStatusCode', + }, 'io.cattle.platform.api.schema.FileSchemaFactoryTest': { 'restoresOnlyNativeReadFieldInPackagedFrozenVolumeRoleSchemas', + 'preservesCreateOnlyApiKeyFlagInThePackagedFrozenV1UserSchema', }, 'io.cattle.platform.schema.processor.VolumeNativeSchemaAuthorizationTest': { 'currentRoleOverlaysExposeNativeClassificationWithoutGrantingMutation', diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index ad28164969..66feb3ad2f 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -4,6 +4,21 @@ The migration preserves established `io.cattle.*` Java packages, Maven coordinat New operator-facing names use PastureStack and `PASTURESTACK_*`. Compatibility identifiers must be changed only with an explicit data migration, a dual-read or dual-write transition, a rollback plan, and cross-repository verification. +## Read-on-create-only responses + +Candidate `0.183.333` applies the existing schema `o` flag only to real resource +creation (`POST` with no action), matching resource-manager dispatch. API Key +deactivate, activate, remove and other POST actions must not return a stored +create-only value. Existing null-redaction representation, ordinary readable +fields, create-time first-secret delivery, v1 frozen schemas and v2 overlays are +preserved. All three response wrappers share one decision. Legacy constructors +without action context keep their signatures but fail closed; callers needing a +creation response must pass the explicit create decision. No database, +authentication, authorization, proxy or stored-state migration is introduced. +Rollback restores the older POST-action exposure and is not a security fix. +Local focused verification passed 19 tests; immutable release and QA8080 native +API Key lifecycle checks are still pending. + ## Volume native classification Published `0.183.332` exposes the existing `volume.isNative` boolean as read-only diff --git a/README.md b/README.md index 7342a7b88c..e628cf2b24 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,17 @@ preserved upstream boundary. ## Current release +Candidate `v0.183.333` corrects a shared response-contract error: a POST action +such as API Key deactivate is not resource creation. Schema fields marked +read-on-create-only are now returned only for a genuine POST create, not for +POST actions. The manager, response writer and attachment helper use the same +request dispatch distinction. Other readable fields and v1/v2 role schemas are +unchanged. Nineteen focused local tests passed (14 new regressions and five +adjacent controls); formal artifact publication and QA8080 native acceptance +are pending. See the [333 candidate note](docs/releases/orchestration-engine-0.183.333.md). + +## Historical 0.183.332 release + Published `v0.183.332` restores the server-owned Volume `isNative` classification as a read-only field in both v1 frozen role schemas and v2 role overlays. It does not change Volume CRUD permissions, infer missing fields in the browser, or diff --git a/code/framework/api-pub-sub-jetty/pom.xml b/code/framework/api-pub-sub-jetty/pom.xml index 542ba068e3..abc9a02471 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/api-pub-sub/pom.xml b/code/framework/api-pub-sub/pom.xml index 3b0345b4ab..5cbfd58fb6 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/api/pom.xml b/code/framework/api/pom.xml index a381c83636..96540730aa 100644 --- a/code/framework/api/pom.xml +++ b/code/framework/api/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/api/src/main/java/io/cattle/platform/api/utils/ApiUtils.java b/code/framework/api/src/main/java/io/cattle/platform/api/utils/ApiUtils.java index 22a8dae8ab..ae0b29fd4b 100644 --- a/code/framework/api/src/main/java/io/cattle/platform/api/utils/ApiUtils.java +++ b/code/framework/api/src/main/java/io/cattle/platform/api/utils/ApiUtils.java @@ -14,6 +14,7 @@ import io.github.ibuildthecloud.gdapi.model.impl.WrappedResource; import io.github.ibuildthecloud.gdapi.request.ApiRequest; import io.github.ibuildthecloud.gdapi.request.resource.ResourceManager; +import io.github.ibuildthecloud.gdapi.util.RequestUtils; import java.util.ArrayList; import java.util.Arrays; @@ -224,7 +225,8 @@ public Object apply(Object input) { additionalFields.putAll(attachments); } String method = request == null ? null : request.getMethod(); - return new WrappedResource(idFormatter, schemaFactory, schema, obj, additionalFields, PRIORITY_FIELDS, method); + return new WrappedResource(idFormatter, schemaFactory, schema, obj, additionalFields, PRIORITY_FIELDS, method, + RequestUtils.isCreateRequest(request)); } finally { DEPTH.set(depth); } diff --git a/code/framework/api/src/test/java/io/cattle/platform/api/schema/FileSchemaFactoryTest.java b/code/framework/api/src/test/java/io/cattle/platform/api/schema/FileSchemaFactoryTest.java index 0cb2ec468b..962ce47579 100644 --- a/code/framework/api/src/test/java/io/cattle/platform/api/schema/FileSchemaFactoryTest.java +++ b/code/framework/api/src/test/java/io/cattle/platform/api/schema/FileSchemaFactoryTest.java @@ -284,6 +284,27 @@ private FileSchemaFactory factory(String resourceName, SchemaFactory schemaFacto return factory; } + @Test + public void preservesCreateOnlyApiKeyFlagInThePackagedFrozenV1UserSchema() throws Exception { + String resourceName = "schema/v1/user.ser"; + Path root = Paths.get("").toAbsolutePath(); + while (root != null && !Files.isRegularFile(root.resolve("resources/content/").resolve(resourceName))) { + root = root.getParent(); + } + assertNotNull("Packaged frozen v1 schema is required", root); + Thread.currentThread().setContextClassLoader(new ResourceClassLoader(resourceName, + Files.readAllBytes(root.resolve("resources/content/").resolve(resourceName)))); + FileSchemaFactory factory = factory(resourceName); + factory.start(); + + Schema keySchema = factory.getSchema("apiKey"); + assertNotNull(keySchema); + FieldImpl field = (FieldImpl) keySchema.getResourceFields().get("secretValue"); + assertNotNull(field); + assertTrue("Frozen v1 must retain the o overlay, not only the dynamic schema", field.isReadOnCreateOnly()); + assertTrue(field.isIncludeInList()); + } + private SchemaImpl schema(String id, String pluralName) { SchemaImpl schema = new SchemaImpl(); schema.setId(id); diff --git a/code/framework/api/src/test/java/io/cattle/platform/api/utils/ApiUtilsCreateOnlyTest.java b/code/framework/api/src/test/java/io/cattle/platform/api/utils/ApiUtilsCreateOnlyTest.java new file mode 100644 index 0000000000..2f3c529532 --- /dev/null +++ b/code/framework/api/src/test/java/io/cattle/platform/api/utils/ApiUtilsCreateOnlyTest.java @@ -0,0 +1,142 @@ +package io.cattle.platform.api.utils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import io.github.ibuildthecloud.gdapi.context.ApiContext; +import io.github.ibuildthecloud.gdapi.factory.impl.SchemaFactoryImpl; +import io.github.ibuildthecloud.gdapi.model.Resource; +import io.github.ibuildthecloud.gdapi.model.impl.FieldImpl; +import io.github.ibuildthecloud.gdapi.model.impl.SchemaImpl; +import io.github.ibuildthecloud.gdapi.request.ApiRequest; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ApiUtilsCreateOnlyTest { + + private SchemaImpl schema; + private final TestBean bean = new TestBean(); + + @Before + public void createContextAndSchema() throws Exception { + ApiContext.newContext().setApiRequest(new ApiRequest(null, null)); + schema = new SchemaImpl(); + schema.setId("apiKey"); + FieldImpl receipt = field("getCreationReceipt"); + receipt.setReadOnCreateOnly(true); + schema.getResourceFields().put("creationReceipt", receipt); + schema.getResourceFields().put("secretValue", field("getSecretValue")); + schema.getResourceFields().put("name", field("getName")); + } + + @After + public void removeContext() { + ApiContext.remove(); + } + + @Test + public void trueCreateAllowsBeanAndAdditionalFieldValuesInBothRoots() { + for (String version : Arrays.asList("v1", "v2-beta")) { + assertFields(render(request(version, "POST", null), null), "unit-bean-receipt"); + Map extra = extras(); + assertFields(render(request(version, "POST", null), extra), "unit-additional-receipt"); + assertEquals("The caller's map must not be consumed", extras(), extra); + } + } + + @Test + public void postActionsRedactBothValueSourcesWithoutRedactingUnflaggedNames() { + for (String version : Arrays.asList("v1", "v2-beta")) { + for (String action : Arrays.asList("deactivate", "activate", "remove", "update", "")) { + assertFields(render(request(version, "POST", action), null), null); + Map extra = extras(); + assertFields(render(request(version, "POST", action), extra), null); + assertEquals(extras(), extra); + } + } + assertEquals("The underlying bean is not scrubbed or mutated", "unit-bean-receipt", bean.getCreationReceipt()); + } + + @Test + public void nullRequestFailsClosedForBothValueSources() { + assertFields(render(null, null), null); + Map extra = extras(); + assertFields(render(null, extra), null); + assertEquals(extras(), extra); + } + + @Test + public void readUpdateAndDeleteCannotUseTheCreateException() { + for (String version : Arrays.asList("v1", "v2-beta")) { + for (String method : Arrays.asList("GET", "PUT", "DELETE")) { + assertFields(render(request(version, method, null), null), null); + assertFields(render(request(version, method, null), extras()), null); + } + } + } + + private FieldImpl field(String getter) throws Exception { + FieldImpl field = new FieldImpl(); + field.setType("string"); + field.setReadMethod(TestBean.class.getMethod(getter)); + return field; + } + + private ApiRequest request(String version, String method, String action) { + ApiRequest request = new ApiRequest(null, null); + request.setVersion(version); + request.setMethod(method); + request.setAction(action); + return request; + } + + private Resource render(ApiRequest request, Map extra) { + return ApiUtils.createResourceWithAttachments(null, request, + ApiContext.getContext().getIdFormatter(), new SchemaFactoryImpl(), schema, bean, extra); + } + + private Map extras() { + Map fields = new LinkedHashMap(); + fields.put("creationReceipt", "unit-additional-receipt"); + return fields; + } + + private void assertFields(Resource resource, String receipt) { + if (receipt == null) { + assertNull(resource.getFields().get("creationReceipt")); + } else { + assertEquals(receipt, resource.getFields().get("creationReceipt")); + } + assertEquals("unit-ordinary-field", resource.getFields().get("secretValue")); + assertEquals("ordinary-name", resource.getFields().get("name")); + } + + public static class TestBean { + public Long getId() { + return 73L; + } + + public Map getData() { + return Collections.emptyMap(); + } + + public String getCreationReceipt() { + return "unit-bean-receipt"; + } + + public String getSecretValue() { + return "unit-ordinary-field"; + } + + public String getName() { + return "ordinary-name"; + } + } +} diff --git a/code/framework/archaius/pom.xml b/code/framework/archaius/pom.xml index d8b4a3cdc2..592abcf4d1 100644 --- a/code/framework/archaius/pom.xml +++ b/code/framework/archaius/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-meta-parent - 0.183.332 + 0.183.333 ../../meta-parent/pom.xml diff --git a/code/framework/async/pom.xml b/code/framework/async/pom.xml index ef369bfb54..c765c45467 100644 --- a/code/framework/async/pom.xml +++ b/code/framework/async/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/auditing/pom.xml b/code/framework/auditing/pom.xml index 70434a0304..0510f82794 100644 --- a/code/framework/auditing/pom.xml +++ b/code/framework/auditing/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/db-loader/pom.xml b/code/framework/db-loader/pom.xml index 1b5d4b7913..7104c15210 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/deferred/pom.xml b/code/framework/deferred/pom.xml index f81f10cd98..77a8d3cd94 100644 --- a/code/framework/deferred/pom.xml +++ b/code/framework/deferred/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/encryption/pom.xml b/code/framework/encryption/pom.xml index 64f5744ae6..2e2fcee6f8 100644 --- a/code/framework/encryption/pom.xml +++ b/code/framework/encryption/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/engine/pom.xml b/code/framework/engine/pom.xml index 2b426f6a34..91ba9b25e6 100644 --- a/code/framework/engine/pom.xml +++ b/code/framework/engine/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/eventing/pom.xml b/code/framework/eventing/pom.xml index 7b2bb95d0e..64b1a7b7e6 100644 --- a/code/framework/eventing/pom.xml +++ b/code/framework/eventing/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/events/pom.xml b/code/framework/events/pom.xml index 891131a182..579214411c 100644 --- a/code/framework/events/pom.xml +++ b/code/framework/events/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/extension-spring/pom.xml b/code/framework/extension-spring/pom.xml index 2ac3b20f77..3068cb5cb9 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/extension/pom.xml b/code/framework/extension/pom.xml index d797540e88..ec376eb44f 100644 --- a/code/framework/extension/pom.xml +++ b/code/framework/extension/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/java-server/pom.xml b/code/framework/java-server/pom.xml index 3c8c0de1e4..572e6b6652 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/model/impl/WrappedResource.java b/code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/model/impl/WrappedResource.java index eb2ca3a8ba..2a179ddcdb 100644 --- a/code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/model/impl/WrappedResource.java +++ b/code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/model/impl/WrappedResource.java @@ -30,13 +30,21 @@ public class WrappedResource extends ResourceImpl implements Resource { Map additionalFields; Map resourceFields; boolean createTsFields = true; - String method; + boolean createResponse; Set priorityFieldNames = null; IdFormatter idFormatter; + /** Legacy callers without request action context fail closed for create-only fields. */ public WrappedResource(IdFormatter idFormatter, SchemaFactory schemaFactory, Schema schema, Object obj, Map additionalFields, Set priorityFieldNames, String method) { + this(idFormatter, schemaFactory, schema, obj, additionalFields, priorityFieldNames, method, false); + } + + /** A create response requires both POST and the caller's explicit create/action decision. */ + public WrappedResource(IdFormatter idFormatter, SchemaFactory schemaFactory, + Schema schema, Object obj, Map additionalFields, + Set priorityFieldNames, String method, boolean createResponse) { super(); this.schemaFactory = schemaFactory; this.schema = schema; @@ -45,7 +53,7 @@ public WrappedResource(IdFormatter idFormatter, SchemaFactory schemaFactory, this.idFormatter = idFormatter; this.additionalFields = additionalFields; this.priorityFieldNames = priorityFieldNames; - this.method = method; + this.createResponse = Schema.Method.POST.isMethod(method) && createResponse; init(); } @@ -53,6 +61,10 @@ public WrappedResource(IdFormatter idFormatter, SchemaFactory schemaFactory, Sch this(idFormatter, schemaFactory, schema, obj, new HashMap(), null, method); } + public WrappedResource(IdFormatter idFormatter, SchemaFactory schemaFactory, Schema schema, Object obj, String method, boolean createResponse) { + this(idFormatter, schemaFactory, schema, obj, new HashMap(), null, method, createResponse); + } + protected void addField(String key, Object value) { if (priorityFieldNames != null && priorityFieldNames.contains(key)) { priorityFields.put(key, value); @@ -75,7 +87,7 @@ protected void init() { if (value == null) { value = field.getValue(obj); } - if (!Schema.Method.POST.isMethod(method) && field.isReadOnCreateOnly()){ + if (!createResponse && field.isReadOnCreateOnly()){ value = null; } if (StringUtils.isNotBlank(field.getTransform()) && StringUtils.isNotBlank((String) value)){ diff --git a/code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/request/resource/impl/AbstractBaseResourceManager.java b/code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/request/resource/impl/AbstractBaseResourceManager.java index 457f9672db..f973338d58 100644 --- a/code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/request/resource/impl/AbstractBaseResourceManager.java +++ b/code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/request/resource/impl/AbstractBaseResourceManager.java @@ -358,7 +358,7 @@ protected void addActions(Object obj, SchemaFactory schemaFactory, Schema schema } protected Resource constructResource(IdFormatter idFormatter, SchemaFactory schemaFactory, Schema schema, Object obj, ApiRequest apiRequest) { - return new WrappedResource(idFormatter, schemaFactory, schema, obj, apiRequest.getMethod()); + return new WrappedResource(idFormatter, schemaFactory, schema, obj, apiRequest.getMethod(), RequestUtils.isCreateRequest(apiRequest)); } protected void addLinks(Object obj, SchemaFactory schemaFactory, Schema schema, Resource resource) { diff --git a/code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/response/JsonResponseWriter.java b/code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/response/JsonResponseWriter.java index 24d68813f3..4a792f295b 100644 --- a/code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/response/JsonResponseWriter.java +++ b/code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/response/JsonResponseWriter.java @@ -11,6 +11,7 @@ import io.github.ibuildthecloud.gdapi.model.impl.WrappedResource; import io.github.ibuildthecloud.gdapi.request.ApiRequest; import io.github.ibuildthecloud.gdapi.request.handler.AbstractApiRequestHandler; +import io.github.ibuildthecloud.gdapi.util.RequestUtils; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; @@ -105,7 +106,9 @@ protected Resource createResource(SchemaFactory schemaFactory, Object obj) { Schema schema = schemaFactory.getSchema(obj.getClass()); ApiContext apiContext = ApiContext.getContext(); - return schema == null ? null : new WrappedResource(apiContext.getIdFormatter(), schemaFactory, schema, obj, apiContext.getApiRequest().getMethod()); + ApiRequest request = apiContext.getApiRequest(); + return schema == null ? null : new WrappedResource(apiContext.getIdFormatter(), schemaFactory, schema, obj, + request.getMethod(), RequestUtils.isCreateRequest(request)); } protected void writeJson(JsonMapper jsonMapper, OutputStream os, Object responseObject, ApiRequest request) throws IOException { diff --git a/code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/util/RequestUtils.java b/code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/util/RequestUtils.java index 687e1a2f2b..1b310b8a70 100644 --- a/code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/util/RequestUtils.java +++ b/code/framework/java-server/src/main/java/io/github/ibuildthecloud/gdapi/util/RequestUtils.java @@ -39,6 +39,11 @@ public static boolean isWriteMethod(String method) { return POST.isMethod(method) || PUT.isMethod(method) || DELETE.isMethod(method); } + /** Uses the same create/action distinction as ResourceManagerRequestHandler. */ + public static boolean isCreateRequest(ApiRequest request) { + return request != null && POST.isMethod(request.getMethod()) && request.getAction() == null; + } + public static boolean mayHaveBody(String method) { return POST.isMethod(method) || PUT.isMethod(method); } diff --git a/code/framework/java-server/src/test/java/io/github/ibuildthecloud/gdapi/model/impl/CreateOnlyCallerTest.java b/code/framework/java-server/src/test/java/io/github/ibuildthecloud/gdapi/model/impl/CreateOnlyCallerTest.java new file mode 100644 index 0000000000..8c1b1e5b1e --- /dev/null +++ b/code/framework/java-server/src/test/java/io/github/ibuildthecloud/gdapi/model/impl/CreateOnlyCallerTest.java @@ -0,0 +1,131 @@ +package io.github.ibuildthecloud.gdapi.model.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import io.github.ibuildthecloud.gdapi.context.ApiContext; +import io.github.ibuildthecloud.gdapi.factory.SchemaFactory; +import io.github.ibuildthecloud.gdapi.factory.impl.SchemaFactoryImpl; +import io.github.ibuildthecloud.gdapi.model.Resource; +import io.github.ibuildthecloud.gdapi.model.Schema; +import io.github.ibuildthecloud.gdapi.request.ApiRequest; +import io.github.ibuildthecloud.gdapi.request.resource.impl.AbstractNoOpResourceManager; +import io.github.ibuildthecloud.gdapi.response.JsonResponseWriter; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class CreateOnlyCallerTest { + + private SchemaFactoryImpl factory; + private Schema schema; + private final TestBean bean = new TestBean(); + + @Before + public void createContextAndBeanSchema() { + ApiContext.newContext(); + factory = new SchemaFactoryImpl(); + factory.setIncludeDefaultTypes(false); + factory.setTypes(Collections.>singletonList(TestBean.class)); + factory.init(); + schema = factory.getSchema(TestBean.class); + FieldImpl receipt = (FieldImpl) schema.getResourceFields().get("creationReceipt"); + assertNotNull("Use the actual bean getter, not additionalFields", receipt.getReadMethod()); + receipt.setReadOnCreateOnly(true); + } + + @After + public void removeContext() { + ApiContext.remove(); + } + + @Test + public void managerConstructResourceUsesTheRequestActionNotOnlyPostMethod() { + ExposedManager manager = new ExposedManager(); + for (String version : Arrays.asList("v1", "v2-beta")) { + assertFields(manager.render(factory, schema, bean, request(version, "POST", null)), true); + for (String action : Arrays.asList("deactivate", "activate", "remove", "update", "")) { + assertFields(manager.render(factory, schema, bean, request(version, "POST", action)), false); + } + for (String method : Arrays.asList("GET", "PUT", "DELETE")) { + assertFields(manager.render(factory, schema, bean, request(version, method, null)), false); + } + } + assertEquals("unit-bean-receipt", bean.getCreationReceipt()); + } + + @Test + public void responseWriterCreateResourceUsesTheActualContextRequest() { + ExposedWriter writer = new ExposedWriter(); + for (String version : Arrays.asList("v1", "v2-beta")) { + ApiContext.getContext().setApiRequest(request(version, "POST", null)); + assertFields(writer.render(factory, bean), true); + for (String action : Arrays.asList("deactivate", "activate", "remove", "update", "")) { + ApiContext.getContext().setApiRequest(request(version, "POST", action)); + assertFields(writer.render(factory, bean), false); + } + for (String method : Arrays.asList("GET", "PUT", "DELETE")) { + ApiContext.getContext().setApiRequest(request(version, method, null)); + assertFields(writer.render(factory, bean), false); + } + } + assertEquals("unit-bean-receipt", bean.getCreationReceipt()); + } + + private ApiRequest request(String version, String method, String action) { + ApiRequest request = new ApiRequest(null, null); + request.setVersion(version); + request.setMethod(method); + request.setAction(action); + return request; + } + + private void assertFields(Resource resource, boolean creation) { + Map fields = resource.getFields(); + if (creation) { + assertEquals("unit-bean-receipt", fields.get("creationReceipt")); + } else { + assertNull(fields.get("creationReceipt")); + } + // Redaction follows the schema's o flag, not a hard-coded secret field name. + assertEquals("unit-ordinary-field", fields.get("secretValue")); + assertEquals("ordinary-name", fields.get("name")); + } + + public static class TestBean { + public String getCreationReceipt() { + return "unit-bean-receipt"; + } + + public String getSecretValue() { + return "unit-ordinary-field"; + } + + public String getName() { + return "ordinary-name"; + } + } + + private static class ExposedManager extends AbstractNoOpResourceManager { + @Override + public Class[] getTypeClasses() { + return new Class[0]; + } + + Resource render(SchemaFactory factory, Schema schema, Object obj, ApiRequest request) { + return super.constructResource(ApiContext.getContext().getIdFormatter(), factory, schema, obj, request); + } + } + + private static class ExposedWriter extends JsonResponseWriter { + Resource render(SchemaFactory factory, Object obj) { + return super.createResource(factory, obj); + } + } +} diff --git a/code/framework/java-server/src/test/java/io/github/ibuildthecloud/gdapi/model/impl/CreateOnlyWrappedResourceTest.java b/code/framework/java-server/src/test/java/io/github/ibuildthecloud/gdapi/model/impl/CreateOnlyWrappedResourceTest.java new file mode 100644 index 0000000000..cdb3503af3 --- /dev/null +++ b/code/framework/java-server/src/test/java/io/github/ibuildthecloud/gdapi/model/impl/CreateOnlyWrappedResourceTest.java @@ -0,0 +1,100 @@ +package io.github.ibuildthecloud.gdapi.model.impl; + +import static org.junit.Assert.*; + +import io.github.ibuildthecloud.gdapi.factory.SchemaFactory; +import io.github.ibuildthecloud.gdapi.factory.impl.SchemaFactoryImpl; +import io.github.ibuildthecloud.gdapi.id.IdFormatter; +import io.github.ibuildthecloud.gdapi.request.ApiRequest; +import io.github.ibuildthecloud.gdapi.util.RequestUtils; +import java.util.HashMap; +import java.util.Map; +import org.junit.Test; + +public class CreateOnlyWrappedResourceTest { + private final IdFormatter ids = new IdFormatter() { + public Object formatId(String type, Object id) { return id; } + public String parseId(String id) { return id; } + public IdFormatter withSchemaFactory(SchemaFactory factory) { return this; } + }; + + private SchemaImpl schema(boolean createOnly) { + SchemaImpl schema = new SchemaImpl(); + schema.setId("apiKey"); + FieldImpl field = new FieldImpl(); + field.setType("string"); + field.setReadOnCreateOnly(createOnly); + schema.getResourceFields().put("secretValue", field); + FieldImpl name = new FieldImpl(); + name.setType("string"); + schema.getResourceFields().put("name", name); + return schema; + } + + private Map values() { + Map values = new HashMap(); + values.put("secretValue", "unit-test-sentinel"); + values.put("name", "ordinary-name"); + return values; + } + + private Map render(String method, String action, String version, boolean createOnly) { + ApiRequest request = new ApiRequest(null, null); + request.setMethod(method); + request.setAction(action); + request.setVersion(version); + return new WrappedResource(ids, new SchemaFactoryImpl(), schema(createOnly), null, values(), null, + request.getMethod(), RequestUtils.isCreateRequest(request)).getFields(); + } + + @Test + public void trueCreationRetainsTheFirstSecretInBothApiVersions() { + for (String version : new String[] {"v1", "v2-beta"}) { + assertEquals("unit-test-sentinel", render("POST", null, version, true).get("secretValue")); + } + } + + @Test + public void postActionsCannotRevealCreateOnlyValues() { + for (String version : new String[] {"v1", "v2-beta"}) { + for (String action : new String[] {"deactivate", "activate", "remove", "update", "", "unknown"}) { + Map result = render("POST", action, version, true); + assertNull(result.get("secretValue")); + assertEquals("ordinary-name", result.get("name")); + } + } + } + + @Test + public void readUpdateDeleteAndUnknownMethodsStillRedact() { + for (String method : new String[] {"GET", "PUT", "DELETE", "HEAD", "OPTIONS", "UNKNOWN", null}) { + assertNull(render(method, null, "v2-beta", true).get("secretValue")); + // A caller cannot make a non-POST request expose creation fields. + assertNull(new WrappedResource(ids, new SchemaFactoryImpl(), schema(true), null, values(), null, + method, true).getFields().get("secretValue")); + } + } + + @Test + public void rolesWithoutCreateOnlyRestrictionKeepTheirExistingReadContract() { + for (String method : new String[] {"GET", "PUT", "DELETE", "POST"}) { + assertEquals("unit-test-sentinel", render(method, "deactivate", "v1", false).get("secretValue")); + } + } + + @Test + public void methodOnlyLegacyConstructorsFailClosedInsteadOfGuessingCreation() { + assertNull(new WrappedResource(ids, new SchemaFactoryImpl(), schema(true), null, values(), null, + "POST").getFields().get("secretValue")); + assertNull(new WrappedResource(ids, new SchemaFactoryImpl(), schema(true), null, "POST") + .getFields().get("secretValue")); + } + + @Test + public void priorityFieldsCannotBypassTheCreateOnlyBoundary() { + Map result = new WrappedResource(ids, new SchemaFactoryImpl(), schema(true), null, + values(), java.util.Collections.singleton("secretValue"), "POST", false).getFields(); + assertNull(result.get("secretValue")); + assertEquals("ordinary-name", result.get("name")); + } +} diff --git a/code/framework/java-server/src/test/java/io/github/ibuildthecloud/gdapi/util/RequestUtilsTest.java b/code/framework/java-server/src/test/java/io/github/ibuildthecloud/gdapi/util/RequestUtilsTest.java index 286720d368..5dd02abb61 100644 --- a/code/framework/java-server/src/test/java/io/github/ibuildthecloud/gdapi/util/RequestUtilsTest.java +++ b/code/framework/java-server/src/test/java/io/github/ibuildthecloud/gdapi/util/RequestUtilsTest.java @@ -8,9 +8,28 @@ import java.util.Map; import org.junit.Test; +import io.github.ibuildthecloud.gdapi.request.ApiRequest; public class RequestUtilsTest { + @Test + public void creationRequiresPostAndAbsentActionNotAStatusCode() { + assertFalse(RequestUtils.isCreateRequest(null)); + ApiRequest request = new ApiRequest(null, null); + assertFalse(RequestUtils.isCreateRequest(request)); + request.setMethod("POST"); + request.setResponseCode(202); + assertTrue(RequestUtils.isCreateRequest(request)); + for (String action : new String[] {"deactivate", "activate", "remove", ""}) { + request.setAction(action); + request.setResponseCode(201); + assertFalse(RequestUtils.isCreateRequest(request)); + } + request.setAction(null); + request.setMethod("PUT"); + assertFalse(RequestUtils.isCreateRequest(request)); + } + @Test public void toMapPreservesStringKeysAndIterationOrder() { Map input = new LinkedHashMap(); diff --git a/code/framework/jmx/pom.xml b/code/framework/jmx/pom.xml index 9e95ab3fa9..c8f7fc3763 100644 --- a/code/framework/jmx/pom.xml +++ b/code/framework/jmx/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/jooq/pom.xml b/code/framework/jooq/pom.xml index e9c7a92c6a..19567b098d 100644 --- a/code/framework/jooq/pom.xml +++ b/code/framework/jooq/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/json/pom.xml b/code/framework/json/pom.xml index 2a429b8355..bd9f083336 100644 --- a/code/framework/json/pom.xml +++ b/code/framework/json/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/launcher/pom.xml b/code/framework/launcher/pom.xml index 4f2be9512f..f4500e4115 100644 --- a/code/framework/launcher/pom.xml +++ b/code/framework/launcher/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/lock/pom.xml b/code/framework/lock/pom.xml index b8e257b0c1..22743a62bd 100644 --- a/code/framework/lock/pom.xml +++ b/code/framework/lock/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/logback/pom.xml b/code/framework/logback/pom.xml index 9dec7da6de..1cb9ab0ff4 100644 --- a/code/framework/logback/pom.xml +++ b/code/framework/logback/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-meta-parent - 0.183.332 + 0.183.333 ../../meta-parent/pom.xml diff --git a/code/framework/managed-context/pom.xml b/code/framework/managed-context/pom.xml index f658a4acf3..588607ba7d 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/metrics/pom.xml b/code/framework/metrics/pom.xml index 1d9a351daa..e7dab54bb0 100644 --- a/code/framework/metrics/pom.xml +++ b/code/framework/metrics/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/module/pom.xml b/code/framework/module/pom.xml index c2085d6e16..e0d418773e 100644 --- a/code/framework/module/pom.xml +++ b/code/framework/module/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/object/pom.xml b/code/framework/object/pom.xml index 562cca778a..42a103c738 100644 --- a/code/framework/object/pom.xml +++ b/code/framework/object/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/pool/pom.xml b/code/framework/pool/pom.xml index e911c39c6d..9eff127eaa 100644 --- a/code/framework/pool/pom.xml +++ b/code/framework/pool/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/resource-monitor/pom.xml b/code/framework/resource-monitor/pom.xml index 4d6e90e47f..1753fde63a 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/schema/pom.xml b/code/framework/schema/pom.xml index af23127f38..db385a8d75 100644 --- a/code/framework/schema/pom.xml +++ b/code/framework/schema/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/server/pom.xml b/code/framework/server/pom.xml index d671c4d71a..97fd0bbe70 100644 --- a/code/framework/server/pom.xml +++ b/code/framework/server/pom.xml @@ -4,7 +4,7 @@ cattle-meta-parent io.cattle - 0.183.332 + 0.183.333 ../../meta-parent/pom.xml diff --git a/code/framework/spring/pom.xml b/code/framework/spring/pom.xml index 51f38984c9..03b952d5d4 100644 --- a/code/framework/spring/pom.xml +++ b/code/framework/spring/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/system-task/pom.xml b/code/framework/system-task/pom.xml index a1c58d9647..81fead9143 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/token/pom.xml b/code/framework/token/pom.xml index a2d0821c9b..5f3d393d0a 100644 --- a/code/framework/token/pom.xml +++ b/code/framework/token/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/framework/utils/pom.xml b/code/framework/utils/pom.xml index 981229acdb..89e337cea4 100644 --- a/code/framework/utils/pom.xml +++ b/code/framework/utils/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/agent-instance/pom.xml b/code/iaas/agent-instance/pom.xml index fbfb2b60f9..ae439e68fe 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/agent-server/pom.xml b/code/iaas/agent-server/pom.xml index 520bc189c4..f6d4d770da 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/agent/pom.xml b/code/iaas/agent/pom.xml index fa3d5a9245..c9fd24457e 100644 --- a/code/iaas/agent/pom.xml +++ b/code/iaas/agent/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/allocator/pom.xml b/code/iaas/allocator/pom.xml index 86143996c9..28b75edf4f 100644 --- a/code/iaas/allocator/pom.xml +++ b/code/iaas/allocator/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/api-logic/pom.xml b/code/iaas/api-logic/pom.xml index 194690cd1f..b5af65f370 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/archaius-management/pom.xml b/code/iaas/archaius-management/pom.xml index 9da2859121..83bee59a9c 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/auth-logic/pom.xml b/code/iaas/auth-logic/pom.xml index 660d1f8e43..e8ca7012fb 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/bootstrap/pom.xml b/code/iaas/bootstrap/pom.xml index fadcd2e069..d443d9cf92 100644 --- a/code/iaas/bootstrap/pom.xml +++ b/code/iaas/bootstrap/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/config-item/api/pom.xml b/code/iaas/config-item/api/pom.xml index eb53a83dce..553eb1db15 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.332 + 0.183.333 ../../../parent/pom.xml diff --git a/code/iaas/config-item/common/pom.xml b/code/iaas/config-item/common/pom.xml index 385fc91300..538410e287 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.332 + 0.183.333 ../../../parent/pom.xml diff --git a/code/iaas/config-item/server/pom.xml b/code/iaas/config-item/server/pom.xml index abd82f0f1e..6d5f1b2599 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.332 + 0.183.333 ../../../parent/pom.xml diff --git a/code/iaas/engine-jooq/pom.xml b/code/iaas/engine-jooq/pom.xml index 69ebc8b2ba..b720a1d725 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/events/pom.xml b/code/iaas/events/pom.xml index e6a34b1925..95f8e12935 100644 --- a/code/iaas/events/pom.xml +++ b/code/iaas/events/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/external-handler/pom.xml b/code/iaas/external-handler/pom.xml index 8661a46b8f..f3825bbc8f 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/ha/pom.xml b/code/iaas/ha/pom.xml index 9161fcbf8b..f2397c5ec4 100644 --- a/code/iaas/ha/pom.xml +++ b/code/iaas/ha/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/healthcheck/pom.xml b/code/iaas/healthcheck/pom.xml index bca1be4570..621f336739 100644 --- a/code/iaas/healthcheck/pom.xml +++ b/code/iaas/healthcheck/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/labels/pom.xml b/code/iaas/labels/pom.xml index 17f885276f..ed592bc678 100644 --- a/code/iaas/labels/pom.xml +++ b/code/iaas/labels/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/logic-common/pom.xml b/code/iaas/logic-common/pom.xml index 1183ffb0f1..a700966f65 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/logic/pom.xml b/code/iaas/logic/pom.xml index 5f5f6e52e4..e03a3b059e 100644 --- a/code/iaas/logic/pom.xml +++ b/code/iaas/logic/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/metadata/pom.xml b/code/iaas/metadata/pom.xml index 7e7c916619..68364ac054 100644 --- a/code/iaas/metadata/pom.xml +++ b/code/iaas/metadata/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/model/pom.xml b/code/iaas/model/pom.xml index 3f2ff636f7..b61bc8ad75 100644 --- a/code/iaas/model/pom.xml +++ b/code/iaas/model/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/resource-pool/pom.xml b/code/iaas/resource-pool/pom.xml index f350eceffe..fc4fd490d8 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/service-discovery/api/pom.xml b/code/iaas/service-discovery/api/pom.xml index e810e41f68..71ca08fcf6 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.332 + 0.183.333 ../../../parent/pom.xml diff --git a/code/iaas/service-discovery/server/pom.xml b/code/iaas/service-discovery/server/pom.xml index 8b21b223e8..4eb9981165 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.332 + 0.183.333 ../../../parent/pom.xml diff --git a/code/iaas/ssh-common/pom.xml b/code/iaas/ssh-common/pom.xml index 3755e1f488..8c0d565ca6 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/storage-service/pom.xml b/code/iaas/storage-service/pom.xml index a18dd3bd70..bd9d0b43f0 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/iaas/task-jooq/pom.xml b/code/iaas/task-jooq/pom.xml index c41976cef6..f4492ad879 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/implementation/activity-log/pom.xml b/code/implementation/activity-log/pom.xml index 2806130e00..74bbcdfde1 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/implementation/agent-instance-impl/pom.xml b/code/implementation/agent-instance-impl/pom.xml index c708dc8149..9655cf590f 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.332 + 0.183.333 ../../parent/pom.xml cattle-agent-instance-impl diff --git a/code/implementation/docker/api/pom.xml b/code/implementation/docker/api/pom.xml index 939dcf286e..fb066d35c4 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.332 + 0.183.333 ../../../parent/pom.xml diff --git a/code/implementation/docker/common/pom.xml b/code/implementation/docker/common/pom.xml index e10c10dd15..4da2857416 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.332 + 0.183.333 ../../../parent/pom.xml diff --git a/code/implementation/docker/compute/pom.xml b/code/implementation/docker/compute/pom.xml index cbf39e750e..ec27fceade 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.332 + 0.183.333 ../../../parent/pom.xml diff --git a/code/implementation/docker/machine/pom.xml b/code/implementation/docker/machine/pom.xml index 8cecef902c..0a0cfb479e 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.332 + 0.183.333 ../../../parent/pom.xml diff --git a/code/implementation/docker/storage/pom.xml b/code/implementation/docker/storage/pom.xml index bf15099056..d8466dee1a 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.332 + 0.183.333 ../../../parent/pom.xml @@ -21,12 +21,12 @@ io.cattle cattle-docker-common - 0.183.332 + 0.183.333 io.cattle cattle-iaas-allocator - 0.183.332 + 0.183.333 diff --git a/code/implementation/extension-api/pom.xml b/code/implementation/extension-api/pom.xml index f324bddbf5..c029794db8 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.332 + 0.183.333 ../../parent/pom.xml cattle-extension-api diff --git a/code/implementation/hazelcast/common/pom.xml b/code/implementation/hazelcast/common/pom.xml index e2e7433263..2917586cf5 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.332 + 0.183.333 ../../../parent/pom.xml diff --git a/code/implementation/hazelcast/eventing/pom.xml b/code/implementation/hazelcast/eventing/pom.xml index fae72b111f..e90dcd6b1c 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.332 + 0.183.333 ../../../parent/pom.xml diff --git a/code/implementation/hazelcast/lock/pom.xml b/code/implementation/hazelcast/lock/pom.xml index c78afd19c5..5f4afac26b 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.332 + 0.183.333 ../../../parent/pom.xml diff --git a/code/implementation/host-api/pom.xml b/code/implementation/host-api/pom.xml index c6b446ada6..21f68027aa 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/implementation/host-stats/pom.xml b/code/implementation/host-stats/pom.xml index 23c95a6e6e..bd7a64185c 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/implementation/register/pom.xml b/code/implementation/register/pom.xml index 117556f4f2..176d71ffca 100644 --- a/code/implementation/register/pom.xml +++ b/code/implementation/register/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/implementation/sample-setup/pom.xml b/code/implementation/sample-setup/pom.xml index 16b7cbad39..e2a6100672 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/implementation/settings-api/pom.xml b/code/implementation/settings-api/pom.xml index ba8f5e2b5f..a9624727b2 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/implementation/simulator/agent-connection/pom.xml b/code/implementation/simulator/agent-connection/pom.xml index 5cdf223aee..9f0932d345 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.332 + 0.183.333 ../../../parent/pom.xml diff --git a/code/implementation/simulator/storage/pom.xml b/code/implementation/simulator/storage/pom.xml index 217938ad55..1d1a5c548b 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.332 + 0.183.333 ../../../parent/pom.xml diff --git a/code/implementation/system-stack/pom.xml b/code/implementation/system-stack/pom.xml index 9803ec614f..6527670533 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.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/implementation/vm/pom.xml b/code/implementation/vm/pom.xml index ded78f922e..a18782dc2d 100644 --- a/code/implementation/vm/pom.xml +++ b/code/implementation/vm/pom.xml @@ -3,7 +3,7 @@ cattle-parent io.cattle - 0.183.332 + 0.183.333 ../../parent/pom.xml cattle-vm diff --git a/code/meta-parent/pom.xml b/code/meta-parent/pom.xml index fcb8e2256a..c48362fb1b 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.332 + 0.183.333 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 9d9ea0f164..f28ef93712 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.332 + 0.183.333 ../../parent/pom.xml jar diff --git a/code/packaging/app/pom.xml b/code/packaging/app/pom.xml index 0ce5ed47e0..6bc471c088 100644 --- a/code/packaging/app/pom.xml +++ b/code/packaging/app/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.332 + 0.183.333 ../../parent/pom.xml war diff --git a/code/packaging/bundle/pom.xml b/code/packaging/bundle/pom.xml index 9cb6c78484..79d7a195d6 100644 --- a/code/packaging/bundle/pom.xml +++ b/code/packaging/bundle/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/packaging/dev/pom.xml b/code/packaging/dev/pom.xml index e478f5e0c9..5328f1d1c3 100644 --- a/code/packaging/dev/pom.xml +++ b/code/packaging/dev/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/packaging/meta/pom.xml b/code/packaging/meta/pom.xml index a3479a6f67..0799e53805 100644 --- a/code/packaging/meta/pom.xml +++ b/code/packaging/meta/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.332 + 0.183.333 ../../parent/pom.xml diff --git a/code/parent/pom.xml b/code/parent/pom.xml index c15fcf2646..e7892aa292 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.332 + 0.183.333 pom diff --git a/docs/releases/orchestration-engine-0.183.333.md b/docs/releases/orchestration-engine-0.183.333.md new file mode 100644 index 0000000000..a938b92d6f --- /dev/null +++ b/docs/releases/orchestration-engine-0.183.333.md @@ -0,0 +1,44 @@ +# Orchestration Engine 0.183.333 — candidate + +## Root cause and scope + +`WrappedResource` treated every POST response as a creation response. An API Key +deactivate action returns the reloaded credential, so its stored `secretValue` +could appear despite the schema's read-on-create-only contract. This observation +does not establish that the stored value was a plaintext secret; it still breaks +the response boundary. GET and PUT redaction did not establish POST-action safety. + +The fix shares the actual resource dispatch distinction (`POST` and no action) +across the manager, JSON writer and attachment helper. Fields marked `o` are +redacted for actions as well as reads, updates and deletes. The existing null +representation is retained. A true create still delivers its first secret. +Other readable fields are not name-matched or hidden; API schemas, role overlays, +account boundaries and persistent data are unchanged. Legacy wrapper signatures +remain available and fail closed without explicit creation context. + +## Focused verification + +Nineteen local tests passed, zero failures/errors/skips: 14 new regressions and +five adjacent controls. Coverage includes true creation, POST actions (including +empty/unknown actions), other methods, both API roots, actual manager/writer and +attachment callers, bean and additional-field values, priority fields, legacy +constructors, ordinary readable fields, the actual frozen v1 user API Key `o` +flag, and the existing Volume formatter contract. An independent read-only +source review found no confirmed surviving bypass or introduced regression in +the five changed production files. The formal release gate requires the new +named regressions in its executed unit-case evidence. + +## Pending acceptance + +This is not yet a published component or Server image. Exact CI artifact +readback, isolated startup, immutable publication, Server packaging and QA8080 +native API Key lifecycle acceptance remain required. Old failed QA receipts stay +HOLD; they are not rewritten as PASS. The full resource/role matrix is incomplete. + +## Upgrade and rollback + +No migration is required. Preserve Compose environment, named volumes, restart +policy, AppArmor, origin and authentication settings. Consume only a new immutable +Server image after the component gates pass; do not overwrite prior tags. A +rollback restores the old POST-action response behavior and must not be described +as preserving this fix. No production deployment is authorized by this candidate. diff --git a/pom.xml b/pom.xml index 238134b00a..d1f0ab913d 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ io.cattle cattle-parent - 0.183.332 + 0.183.333 code/parent/pom.xml cattle diff --git a/resources/pom.xml b/resources/pom.xml index 6568faff64..eb821685ae 100644 --- a/resources/pom.xml +++ b/resources/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.332 + 0.183.333 ../code/parent/pom.xml diff --git a/scripts/build b/scripts/build index fd96e0e1aa..97b0cd353b 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.332} +ENGINE_VERSION=${ENGINE_VERSION:-0.183.333} case "$SOURCE_REVISION" in ''|*[!0-9a-f]*) diff --git a/scripts/check-pasturestack-source b/scripts/check-pasturestack-source index fa4ba9ceec..7d5f953ce4 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.332' +require_line code/meta-parent/pom.xml ' 0.183.333' 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' @@ -418,4 +418,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.332 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.333 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 742fb4d6d0..03efc3bc38 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.332} +expected_version=${EXPECTED_ENGINE_VERSION:-0.183.333} test -f "$artifact" artifact=$(realpath "$artifact")