Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/security-release-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
15 changes: 15 additions & 0 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion code/framework/api-pub-sub-jetty/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>io.cattle</groupId>
<artifactId>cattle-parent</artifactId>
<version>0.183.332</version>
<version>0.183.333</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
<dependencies>
Expand Down
2 changes: 1 addition & 1 deletion code/framework/api-pub-sub/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>io.cattle</groupId>
<artifactId>cattle-parent</artifactId>
<version>0.183.332</version>
<version>0.183.333</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
<dependencies>
Expand Down
2 changes: 1 addition & 1 deletion code/framework/api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<artifactId>cattle-parent</artifactId>
<groupId>io.cattle</groupId>
<version>0.183.332</version>
<version>0.183.333</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> extra) {
return ApiUtils.createResourceWithAttachments(null, request,
ApiContext.getContext().getIdFormatter(), new SchemaFactoryImpl(), schema, bean, extra);
}

private Map<String, Object> extras() {
Map<String, Object> fields = new LinkedHashMap<String, Object>();
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<String, Object> getData() {
return Collections.emptyMap();
}

public String getCreationReceipt() {
return "unit-bean-receipt";
}

public String getSecretValue() {
return "unit-ordinary-field";
}

public String getName() {
return "ordinary-name";
}
}
}
2 changes: 1 addition & 1 deletion code/framework/archaius/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>io.cattle</groupId>
<artifactId>cattle-meta-parent</artifactId>
<version>0.183.332</version>
<version>0.183.333</version>
<relativePath>../../meta-parent/pom.xml</relativePath>
</parent>
</project>
2 changes: 1 addition & 1 deletion code/framework/async/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>io.cattle</groupId>
<artifactId>cattle-parent</artifactId>
<version>0.183.332</version>
<version>0.183.333</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
<dependencies>
Expand Down
2 changes: 1 addition & 1 deletion code/framework/auditing/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<artifactId>cattle-parent</artifactId>
<groupId>io.cattle</groupId>
<version>0.183.332</version>
<version>0.183.333</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion code/framework/db-loader/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>io.cattle</groupId>
<artifactId>cattle-parent</artifactId>
<version>0.183.332</version>
<version>0.183.333</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
<dependencies>
Expand Down
2 changes: 1 addition & 1 deletion code/framework/deferred/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>io.cattle</groupId>
<artifactId>cattle-parent</artifactId>
<version>0.183.332</version>
<version>0.183.333</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
<dependencies>
Expand Down
2 changes: 1 addition & 1 deletion code/framework/encryption/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>io.cattle</groupId>
<artifactId>cattle-parent</artifactId>
<version>0.183.332</version>
<version>0.183.333</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
<dependencies>
Expand Down
2 changes: 1 addition & 1 deletion code/framework/engine/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>io.cattle</groupId>
<artifactId>cattle-parent</artifactId>
<version>0.183.332</version>
<version>0.183.333</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
<dependencies>
Expand Down
2 changes: 1 addition & 1 deletion code/framework/eventing/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>io.cattle</groupId>
<artifactId>cattle-parent</artifactId>
<version>0.183.332</version>
<version>0.183.333</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
<dependencies>
Expand Down
2 changes: 1 addition & 1 deletion code/framework/events/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>io.cattle</groupId>
<artifactId>cattle-parent</artifactId>
<version>0.183.332</version>
<version>0.183.333</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
<dependencies>
Expand Down
2 changes: 1 addition & 1 deletion code/framework/extension-spring/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<artifactId>cattle-parent</artifactId>
<groupId>io.cattle</groupId>
<version>0.183.332</version>
<version>0.183.333</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
<dependencies>
Expand Down
2 changes: 1 addition & 1 deletion code/framework/extension/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<artifactId>cattle-parent</artifactId>
<groupId>io.cattle</groupId>
<version>0.183.332</version>
<version>0.183.333</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
<dependencies>
Expand Down
2 changes: 1 addition & 1 deletion code/framework/java-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>io.cattle</groupId>
<artifactId>cattle-parent</artifactId>
<version>0.183.332</version>
<version>0.183.333</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
<dependencies>
Expand Down
Loading
Loading