Skip to content
Closed
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
14 changes: 14 additions & 0 deletions .github/workflows/security-release-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@ jobs:
import xml.etree.ElementTree as ET

required = {
'io.cattle.platform.api.schema.FileSchemaFactoryTest': {
'restoresOnlyNativeReadFieldInPackagedFrozenVolumeRoleSchemas',
},
'io.cattle.platform.schema.processor.VolumeNativeSchemaAuthorizationTest': {
'currentRoleOverlaysExposeNativeClassificationWithoutGrantingMutation',
'readonlyPipelinePreservesCrudWhileRetainingServerOwnedNativeReadField',
},
'io.github.ibuildthecloud.gdapi.model.impl.VolumeNativeWrappedResourceTest': {
'formatterRetainsTheActualServerTrueAndFalseClassification',
'onlyAnExplicitSchemaContractCanProvideTheServerDefault',
},
'io.github.ibuildthecloud.gdapi.validation.ValidationHandlerTest': {
'clientCannotSetServerOwnedVolumeNativeClassification',
},
'io.cattle.platform.core.dao.impl.NativeContainerNameRefreshDaoTest': {
'exactExistingImportedSnapshotAndNameOnlyCas',
'compareAndSwapContainsEveryOriginalColumnIncludingBinaryTextAndJson',
Expand Down
14 changes: 14 additions & 0 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ 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.

## Volume native classification

Candidate `0.183.332` exposes the existing `volume.isNative` boolean as read-only
for readable Volume resources. Current role overlays retain the field; the v1
loader copies only this missing field from the current core schema into frozen
role schemas. It grants neither create nor update permission on the field and
does not widen collection/object authorization, methods, actions or other
fields. The API preserves stored true/false values and applies the established
server default only through an explicit schema contract. Client input cannot
set this server-owned classification on POST or PUT. No database migration is
required. Rolling back removes the field from affected role responses, so the
strict Web Console unallocated-volume list may again hide eligible volumes.
Native UI lifecycle acceptance and artifact publication are still pending.

## Certificate lifecycle

Engine `0.183.327` allows partial Certificate updates that omit `cert`, without
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ preserved upstream boundary.

## Current release

Candidate `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
allow clients to change native classification. Eighteen targeted local tests
passed, including six new regressions. Formal artifact publication and native
Volume UI lifecycle acceptance are pending; see the
[332 candidate note](docs/releases/orchestration-engine-0.183.332.md).

The published release `v0.183.331` corrects the stopped-container mapping condition
in 330. The common selection/update predicate accepts only running/active
or stopped/inactive pairs; all source-account, unique-mapping, managed-container
Expand Down Expand Up @@ -206,7 +214,7 @@ bash scripts/check-cattle-jdk25-full-package
After the gate passes, package and check the release artifact:

```sh
ENGINE_VERSION=0.183.331 bash scripts/build --release
ENGINE_VERSION=0.183.332 bash scripts/build --release
bash scripts/check-release-artifact dist/artifacts/cattle.jar
```

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.331</version>
<version>0.183.332</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.331</version>
<version>0.183.332</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.331</version>
<version>0.183.332</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ protected void copyAccessors(Schema schema) {
Schema coreSchema = parentSchemaFactory.getSchema(schema.getId());
mergeProjectMemberExternalIdTypeOptions(schema, coreSchema);
mergeProjectTemplatePublicReadField(schema, coreSchema);
mergeVolumeNativeReadField(schema, coreSchema);
Class<?> clz = parentSchemaFactory.getSchemaClass(schema.getId());
if (clz == null) {
return;
Expand Down Expand Up @@ -157,6 +158,29 @@ public String getFile() {
return file;
}

protected void mergeVolumeNativeReadField(Schema schema, Schema parentSchema) {
if (parentSchema == null || !"volume".equals(schema.getId()) ||
schema.getResourceFields().containsKey("isNative")) {
return;
}

Field parentField = parentSchema.getResourceFields().get("isNative");
if (!(parentField instanceof FieldImpl)) {
return;
}

// v1 reads frozen role schemas, so the current authorization overlay
// cannot restore this missing classification. Expose only the existing
// server-owned flag; do not grant mutation or replace other fields.
FieldImpl readOnly = new FieldImpl(parentField);
readOnly.setName("isNative");
readOnly.setCreate(false);
readOnly.setUpdate(false);
readOnly.setReadOnCreateOnly(false);
readOnly.setIncludeInList(true);
schema.getResourceFields().put("isNative", readOnly);
}

public void setFile(String file) {
this.file = file;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,71 @@ private FileSchemaFactory factory(String resourceName) {
return factory(resourceName, new EmptySchemaFactory());
}

@Test
public void restoresOnlyNativeReadFieldInPackagedFrozenVolumeRoleSchemas() throws Exception {
SchemaImpl core = schema("volume", "volumes");
FieldImpl coreField = new FieldImpl();
coreField.setName("isNative");
coreField.setType("boolean");
coreField.setDefault(Boolean.FALSE);
// Even a more permissive parent cannot grant writes through this merge.
coreField.setCreate(true);
coreField.setUpdate(true);
core.getResourceFields().put("isNative", coreField);
Path root = Paths.get("").toAbsolutePath();
while (root != null && !Files.isRegularFile(root.resolve("resources/content/schema/v1/owner.ser"))) {
root = root.getParent();
}
assertNotNull("Packaged frozen role schemas are required", root);
for (String role : Arrays.asList("owner", "member", "readonly", "restricted", "user", "admin")) {
String resourceName = "schema/v1/" + role + ".ser";
byte[] bytes = Files.readAllBytes(root.resolve("resources/content/").resolve(resourceName));
Thread.currentThread().setContextClassLoader(new ResourceClassLoader(resourceName, bytes));
FileSchemaFactory original = factory(resourceName);
original.start();
Thread.currentThread().setContextClassLoader(new ResourceClassLoader(resourceName, bytes));
FileSchemaFactory repaired = factory(resourceName, new SingleSchemaFactory(core));
repaired.start();

Schema before = original.getSchema("volume"), after = repaired.getSchema("volume");
assertNotNull(role, before);
assertNotNull(role, after);
Map<String, FieldImpl> originalFields = new LinkedHashMap<String, FieldImpl>();
for (Map.Entry<String, io.github.ibuildthecloud.gdapi.model.Field> entry : before.getResourceFields().entrySet()) {
originalFields.put(entry.getKey(), (FieldImpl) entry.getValue());
}
FieldImpl actual = (FieldImpl) after.getResourceFields().get("isNative");
assertNotNull(role, actual);
assertEquals(role, "boolean", actual.getType());
assertEquals(role, Boolean.FALSE, actual.getDefault());
assertFalse(role, actual.isNullable());
assertFalse(role, actual.isCreate());
assertFalse(role, actual.isUpdate());
assertFalse(role, actual.isReadOnCreateOnly());
assertTrue(role, actual.isIncludeInList());
assertEquals(role, before.getCollectionMethods(), after.getCollectionMethods());
assertEquals(role, before.getResourceMethods(), after.getResourceMethods());
assertTrue(role + "/resourceActions", Arrays.equals(
serialize(Arrays.<Object>asList(before.getResourceActions())),
serialize(Arrays.<Object>asList(after.getResourceActions()))));
assertTrue(role + "/collectionActions", Arrays.equals(
serialize(Arrays.<Object>asList(before.getCollectionActions())),
serialize(Arrays.<Object>asList(after.getCollectionActions()))));
for (Map.Entry<String, FieldImpl> entry : originalFields.entrySet()) {
if (!"isNative".equals(entry.getKey())) {
assertTrue(role, after.getResourceFields().containsKey(entry.getKey()));
assertTrue(role + "/" + entry.getKey(), Arrays.equals(
serialize(Arrays.<Object>asList(entry.getValue())),
serialize(Arrays.<Object>asList(after.getResourceFields().get(entry.getKey())))));
}
}
assertEquals(role, originalFields.size() + (originalFields.containsKey("isNative") ? 0 : 1),
after.getResourceFields().size());
}
assertTrue("Parent permissions must remain unchanged", coreField.isCreate());
assertTrue(coreField.isUpdate());
}

private FileSchemaFactory factory(String resourceName, SchemaFactory schemaFactory) {
FileSchemaFactory factory = new FileSchemaFactory();
factory.setFile(resourceName);
Expand Down
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.331</version>
<version>0.183.332</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.331</version>
<version>0.183.332</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.331</version>
<version>0.183.332</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.331</version>
<version>0.183.332</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.331</version>
<version>0.183.332</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.331</version>
<version>0.183.332</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.331</version>
<version>0.183.332</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.331</version>
<version>0.183.332</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.331</version>
<version>0.183.332</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.331</version>
<version>0.183.332</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.331</version>
<version>0.183.332</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.331</version>
<version>0.183.332</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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 java.util.HashMap;
import java.util.Map;
import org.junit.Test;

public class VolumeNativeWrappedResourceTest {
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 Map<String, Object> render(boolean declared, Object actual, boolean present) {
SchemaImpl schema = new SchemaImpl();
schema.setId("volume");
if (declared) {
FieldImpl flag = new FieldImpl();
flag.setType("boolean");
flag.setDefault(Boolean.FALSE);
schema.getResourceFields().put("isNative", flag);
}
Map<String, Object> values = new HashMap<String, Object>();
if (present) values.put("isNative", actual);
return new WrappedResource(ids, new SchemaFactoryImpl(), schema, null, values, null, "GET").getFields();
}

@Test
public void formatterRetainsTheActualServerTrueAndFalseClassification() {
assertEquals(Boolean.TRUE, render(true, Boolean.TRUE, true).get("isNative"));
assertEquals(Boolean.FALSE, render(true, Boolean.FALSE, true).get("isNative"));
}

@Test
public void onlyAnExplicitSchemaContractCanProvideTheServerDefault() {
assertEquals(Boolean.FALSE, render(true, null, false).get("isNative"));
assertFalse(render(false, Boolean.FALSE, true).containsKey("isNative"));
assertFalse(render(false, Boolean.TRUE, true).containsKey("isNative"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,28 @@

public class ValidationHandlerTest {

@Test
public void clientCannotSetServerOwnedVolumeNativeClassification() {
SchemaImpl schema = new SchemaImpl();
schema.setId("volume");
FieldImpl flag = new FieldImpl();
flag.setType("boolean");
flag.setDefault(Boolean.FALSE);
schema.getResourceFields().put("isNative", flag);
ValidationHandler handler = new ValidationHandler();
for (Object attempt : Arrays.asList(Boolean.TRUE, Boolean.FALSE, "true", null)) {
Map<String, Object> input = new HashMap<String, Object>();
input.put("isNative", attempt);
ApiRequest request = new ApiRequest(null, null);
request.setRequestObject(input);
handler.validateOperationField(schema, request, true, new ValidationContext());
assertEquals(Boolean.FALSE, RequestUtils.toMap(request.getRequestObject()).get("isNative"));
request.setRequestObject(input);
handler.validateOperationField(schema, request, false, new ValidationContext());
assertFalse(RequestUtils.toMap(request.getRequestObject()).containsKey("isNative"));
}
}

@Test
public void testNullableOption() {
SchemaImpl schema = new SchemaImpl();
Expand Down
2 changes: 1 addition & 1 deletion code/framework/jmx/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.331</version>
<version>0.183.332</version>
<relativePath>../../parent/pom.xml</relativePath>
</parent>
<dependencies>
Expand Down
Loading
Loading