diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index cea0e9411a..fe5847076c 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -4,6 +4,16 @@ 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. +## Certificate lifecycle + +Engine `0.183.327` allows partial Certificate updates that omit `cert`, without +changing existing certificate or key fields. Explicit invalid `cert` values +still fail validation. The shared DELETE/remove guard also rejects v2 alternate +certificate references using the same error contract as default references. +The v1 helper path, account boundary, removed/kind query constraints, and +account teardown remain unchanged. No migration is required; rollback restores +the old partial-update failure and alternate-reference protection gap. + ## Docker host policy Release `0.183.319` preserves the Docker host policy introduced in `0.183.299`, diff --git a/Dockerfile.dapper b/Dockerfile.dapper index bbe2adb0a2..855cc9e2dc 100644 --- a/Dockerfile.dapper +++ b/Dockerfile.dapper @@ -83,8 +83,19 @@ RUN set -eux; \ tox="${UBUNTU_APT_TOX_VERSION}" \ unzip="${UBUNTU_APT_UNZIP_VERSION}" \ xz-utils="${UBUNTU_APT_XZ_UTILS_VERSION}"; \ + printf 'Types: deb\nURIs: https://snapshot.ubuntu.com/ubuntu/%s\nSuites: resolute-security\nComponents: main\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\nSnapshot: no\n' \ + "${UBUNTU_APT_SECURITY_SNAPSHOT}" > /etc/apt/sources.list.d/pasturestack-security.sources; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + libssl3t64="${UBUNTU_APT_OPENSSL_VERSION}" \ + openssl="${UBUNTU_APT_OPENSSL_VERSION}" \ + openssl-provider-legacy="${UBUNTU_APT_OPENSSL_VERSION}"; \ + for package in libssl3t64 openssl openssl-provider-legacy; do \ + test "$(dpkg-query -W -f='${Version}' "${package}")" = "${UBUNTU_APT_OPENSSL_VERSION}"; \ + done; \ { \ printf 'snapshot\t%s\n' "${UBUNTU_APT_SNAPSHOT}"; \ + printf 'security-snapshot\t%s\n' "${UBUNTU_APT_SECURITY_SNAPSHOT}"; \ dpkg-query -W -f='${binary:Package}\t${Version}\n' | LC_ALL=C sort; \ } > /licenses/ORCHESTRATION-ENGINE-UBUNTU-APT-PACKAGES.tsv; \ apt-get clean; \ diff --git a/README.md b/README.md index a93b03166a..2bb904d891 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,12 @@ preserved upstream boundary. ## Current release -The latest public Engine release is -[`v0.183.326`](https://github.com/PastureStack/orchestration-engine/releases/tag/v0.183.326). -It restores read-only `projectTemplate.isPublic` in both v1 and v2-beta -responses for non-admin readers and omits unavailable remove actions on -non-owned templates. See the -[release note](docs/releases/orchestration-engine-0.183.326.md) for behavior, +The current release source targets `v0.183.327`. +Certificate name/description updates preserve omitted certificate content; +deleting or removing a certificate referenced by a v2 load balancer's alternate +list is rejected just like a default certificate. Authorization and private-key +masking are unchanged. See the +[release note](docs/releases/orchestration-engine-0.183.327.md) for behavior, tests, and compatibility details. Previous release notes remain in [`docs/releases`](docs/releases), and the [GitHub release history](https://github.com/PastureStack/orchestration-engine/releases) @@ -27,7 +27,12 @@ records published artifacts. The `v0.183.324` tag is source-only and has no published release artifact. The build retains Java 25, Ubuntu 26.04, Maven, Liquibase, MariaDB/MySQL, -WebSocket, concurrency, and runtime maintenance. It consumes the exact +WebSocket, concurrency, runtime maintenance, and the existing direct tool +versions. Its signed Ubuntu security snapshot +pins OpenSSL CLI, library and legacy provider to `3.5.5-1ubuntu3.6`, the +official fix for [CVE-2026-84782](https://ubuntu.com/security/CVE-2026-84782). +It retains OpenSSL 3.5 and does not relax the build-image security gate. +It consumes the exact `5.7.4` JAR from [`distributed-cache-runtime`](https://github.com/PastureStack/distributed-cache-runtime/releases/tag/v5.7.4) and verifies its pinned digest and dependency metadata before installing it @@ -46,7 +51,7 @@ bash scripts/check-cattle-jdk25-full-package After the gate passes, package and check the release artifact: ```sh -ENGINE_VERSION=0.183.326 bash scripts/build --release +ENGINE_VERSION=0.183.327 bash scripts/build --release bash scripts/check-release-artifact dist/artifacts/cattle.jar ``` diff --git a/code/framework/api-pub-sub-jetty/pom.xml b/code/framework/api-pub-sub-jetty/pom.xml index 0216a00a54..4bbbe6abf3 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/api-pub-sub/pom.xml b/code/framework/api-pub-sub/pom.xml index 5a0df01a68..26ecc4d2d5 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/api/pom.xml b/code/framework/api/pom.xml index 4bf06b4581..721afbc4e7 100644 --- a/code/framework/api/pom.xml +++ b/code/framework/api/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/archaius/pom.xml b/code/framework/archaius/pom.xml index 525784264d..de1db46d2c 100644 --- a/code/framework/archaius/pom.xml +++ b/code/framework/archaius/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-meta-parent - 0.183.326 + 0.183.327 ../../meta-parent/pom.xml diff --git a/code/framework/async/pom.xml b/code/framework/async/pom.xml index a5a21797e1..a4a4f31186 100644 --- a/code/framework/async/pom.xml +++ b/code/framework/async/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/auditing/pom.xml b/code/framework/auditing/pom.xml index b03e53af39..8b11856ac8 100644 --- a/code/framework/auditing/pom.xml +++ b/code/framework/auditing/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/db-loader/pom.xml b/code/framework/db-loader/pom.xml index d6ac04cc9b..cb17853ecd 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/deferred/pom.xml b/code/framework/deferred/pom.xml index dbfe31cf83..221aaa0813 100644 --- a/code/framework/deferred/pom.xml +++ b/code/framework/deferred/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/encryption/pom.xml b/code/framework/encryption/pom.xml index 17b812bbba..31e83da012 100644 --- a/code/framework/encryption/pom.xml +++ b/code/framework/encryption/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/engine/pom.xml b/code/framework/engine/pom.xml index 06fdc102fd..bad41118ec 100644 --- a/code/framework/engine/pom.xml +++ b/code/framework/engine/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/eventing/pom.xml b/code/framework/eventing/pom.xml index 04af361d3d..935497b788 100644 --- a/code/framework/eventing/pom.xml +++ b/code/framework/eventing/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/events/pom.xml b/code/framework/events/pom.xml index 6155862d37..4880c68e8a 100644 --- a/code/framework/events/pom.xml +++ b/code/framework/events/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/extension-spring/pom.xml b/code/framework/extension-spring/pom.xml index 8c3a6c5090..6dd2d52cf8 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/extension/pom.xml b/code/framework/extension/pom.xml index 82bc54b1aa..c45484818d 100644 --- a/code/framework/extension/pom.xml +++ b/code/framework/extension/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/java-server/pom.xml b/code/framework/java-server/pom.xml index 9e316931ed..3e8ee9cf8d 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/jmx/pom.xml b/code/framework/jmx/pom.xml index 5d62cb0f25..515a92ec32 100644 --- a/code/framework/jmx/pom.xml +++ b/code/framework/jmx/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/jooq/pom.xml b/code/framework/jooq/pom.xml index 3e186b8d49..e274086141 100644 --- a/code/framework/jooq/pom.xml +++ b/code/framework/jooq/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/json/pom.xml b/code/framework/json/pom.xml index cf60d7433e..ed1ee071b9 100644 --- a/code/framework/json/pom.xml +++ b/code/framework/json/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/launcher/pom.xml b/code/framework/launcher/pom.xml index e06840ff83..03e1dc8111 100644 --- a/code/framework/launcher/pom.xml +++ b/code/framework/launcher/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/lock/pom.xml b/code/framework/lock/pom.xml index 4d21745aea..93818f3b0d 100644 --- a/code/framework/lock/pom.xml +++ b/code/framework/lock/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/logback/pom.xml b/code/framework/logback/pom.xml index dbd9300157..8101530b63 100644 --- a/code/framework/logback/pom.xml +++ b/code/framework/logback/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-meta-parent - 0.183.326 + 0.183.327 ../../meta-parent/pom.xml diff --git a/code/framework/managed-context/pom.xml b/code/framework/managed-context/pom.xml index 7e3d3dfe29..356238abf4 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/metrics/pom.xml b/code/framework/metrics/pom.xml index 52a4c920b4..def5892f78 100644 --- a/code/framework/metrics/pom.xml +++ b/code/framework/metrics/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/module/pom.xml b/code/framework/module/pom.xml index 4ce929b902..7c36984612 100644 --- a/code/framework/module/pom.xml +++ b/code/framework/module/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/object/pom.xml b/code/framework/object/pom.xml index 9a55adb940..61280b2654 100644 --- a/code/framework/object/pom.xml +++ b/code/framework/object/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/pool/pom.xml b/code/framework/pool/pom.xml index 82a20725f4..aeb1356b4e 100644 --- a/code/framework/pool/pom.xml +++ b/code/framework/pool/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/resource-monitor/pom.xml b/code/framework/resource-monitor/pom.xml index 9ec6430f40..8682b7e64d 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/schema/pom.xml b/code/framework/schema/pom.xml index 54a980ed7e..61db3db272 100644 --- a/code/framework/schema/pom.xml +++ b/code/framework/schema/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/server/pom.xml b/code/framework/server/pom.xml index 8c744b6639..9b51ae6d91 100644 --- a/code/framework/server/pom.xml +++ b/code/framework/server/pom.xml @@ -4,7 +4,7 @@ cattle-meta-parent io.cattle - 0.183.326 + 0.183.327 ../../meta-parent/pom.xml diff --git a/code/framework/spring/pom.xml b/code/framework/spring/pom.xml index 49263fe08c..2726e32570 100644 --- a/code/framework/spring/pom.xml +++ b/code/framework/spring/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/system-task/pom.xml b/code/framework/system-task/pom.xml index 25aba47d93..9ed64beb0d 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/token/pom.xml b/code/framework/token/pom.xml index 0863e21602..729b879ed0 100644 --- a/code/framework/token/pom.xml +++ b/code/framework/token/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/framework/utils/pom.xml b/code/framework/utils/pom.xml index ce2243c07c..100f13e57b 100644 --- a/code/framework/utils/pom.xml +++ b/code/framework/utils/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/agent-instance/pom.xml b/code/iaas/agent-instance/pom.xml index bb7263c5bd..7086c7f511 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/agent-server/pom.xml b/code/iaas/agent-server/pom.xml index bdaf6eec0c..6f2bf0aa3b 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/agent/pom.xml b/code/iaas/agent/pom.xml index b047b4634a..76a925744c 100644 --- a/code/iaas/agent/pom.xml +++ b/code/iaas/agent/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/allocator/pom.xml b/code/iaas/allocator/pom.xml index 881bff17a7..6b25fc2916 100644 --- a/code/iaas/allocator/pom.xml +++ b/code/iaas/allocator/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/api-logic/pom.xml b/code/iaas/api-logic/pom.xml index b372ada87b..a828cf560b 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/api-logic/src/main/java/io/cattle/platform/iaas/api/filter/ssl/CertificateCreateValidationFilter.java b/code/iaas/api-logic/src/main/java/io/cattle/platform/iaas/api/filter/ssl/CertificateCreateValidationFilter.java index 0186951e5b..ccb07c3bb7 100644 --- a/code/iaas/api-logic/src/main/java/io/cattle/platform/iaas/api/filter/ssl/CertificateCreateValidationFilter.java +++ b/code/iaas/api-logic/src/main/java/io/cattle/platform/iaas/api/filter/ssl/CertificateCreateValidationFilter.java @@ -5,6 +5,7 @@ import io.cattle.platform.iaas.api.filter.common.AbstractDefaultResourceManagerFilter; import io.cattle.platform.object.util.DataUtils; import io.cattle.platform.ssh.common.SslCertificateUtils; +import io.cattle.platform.util.type.CollectionUtils; import io.github.ibuildthecloud.gdapi.exception.ClientVisibleException; import io.github.ibuildthecloud.gdapi.request.ApiRequest; import io.github.ibuildthecloud.gdapi.request.resource.ResourceManager; @@ -39,10 +40,11 @@ public Object create(String type, ApiRequest request, ResourceManager next) { @Override public Object update(String type, String id, ApiRequest request, ResourceManager next) { - String cert = DataUtils.getFieldFromRequest(request, "cert", String.class); - - Certificate certificate = request.proxyRequestObject(Certificate.class); - setCertificateFields(cert, certificate); + if (CollectionUtils.toMap(request.getRequestObject()).containsKey("cert")) { + String cert = DataUtils.getFieldFromRequest(request, "cert", String.class); + Certificate certificate = request.proxyRequestObject(Certificate.class); + setCertificateFields(cert, certificate); + } return super.update(type, id, request, next); } diff --git a/code/iaas/api-logic/src/test/java/io/cattle/platform/iaas/api/filter/ssl/CertificateCreateValidationFilterTest.java b/code/iaas/api-logic/src/test/java/io/cattle/platform/iaas/api/filter/ssl/CertificateCreateValidationFilterTest.java new file mode 100644 index 0000000000..d43cf7997f --- /dev/null +++ b/code/iaas/api-logic/src/test/java/io/cattle/platform/iaas/api/filter/ssl/CertificateCreateValidationFilterTest.java @@ -0,0 +1,246 @@ +package io.cattle.platform.iaas.api.filter.ssl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; + +import io.cattle.platform.core.model.Certificate; +import io.cattle.platform.object.util.DataUtils; +import io.cattle.platform.ssh.common.SslCertificateUtils; +import io.cattle.platform.util.type.CollectionUtils; +import io.github.ibuildthecloud.gdapi.exception.ClientVisibleException; +import io.github.ibuildthecloud.gdapi.request.ApiRequest; +import io.github.ibuildthecloud.gdapi.request.resource.ResourceManager; +import io.github.ibuildthecloud.gdapi.util.ResponseCodes; +import io.github.ibuildthecloud.gdapi.validation.ValidationErrorCodes; + +import java.io.StringWriter; +import java.lang.reflect.Proxy; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.Security; +import java.util.Arrays; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class CertificateCreateValidationFilterTest { + private static final String[] DERIVED = { "certFingerprint", "expiresAt", "CN", "issuer", "issuedAt", + "version", "algorithm", "serialNumber", "keySize", "subjectAlternativeNames" }; + private static String pem; + private static boolean addedProvider; + + @BeforeClass + public static void createSyntheticPublicCertificate() throws Exception { + addedProvider = Security.getProvider("BC") == null; + if (addedProvider) Security.addProvider(new BouncyCastleProvider()); + KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); + generator.initialize(2048); + KeyPair pair = generator.generateKeyPair(); + X500Name name = new X500Name("CN=certificate-regression.invalid,O=Offline Test"); + JcaX509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(name, + BigInteger.valueOf(42), new Date(1700000000000L), new Date(2000000000000L), name, pair.getPublic()); + builder.addExtension(Extension.subjectAlternativeName, false, + new GeneralNames(new GeneralName(GeneralName.dNSName, "certificate-regression.invalid"))); + X509CertificateHolder certificate = builder.build(new JcaContentSignerBuilder("SHA256withRSA") + .setProvider("BC").build(pair.getPrivate())); + StringWriter text = new StringWriter(); + try (JcaPEMWriter writer = new JcaPEMWriter(text)) { + writer.writeObject(certificate); + } + pem = text.toString(); // Public synthetic certificate only; no private key is persisted. + } + + @AfterClass + public static void restoreProviderRegistration() { + if (addedProvider) Security.removeProvider("BC"); + } + + @Test + public void nameOnlyUpdateForwardsExactRequestWithoutCertificateMaterialOrDerivedFields() { + assertOmittedCertForwarded("name", "renamed-certificate"); + } + + @Test + public void descriptionOnlyUpdateForwardsExactRequestWithoutCertificateMaterialOrDerivedFields() { + assertOmittedCertForwarded("description", "updated description"); + } + + @Test + public void omittedCertificatePreservesAllExistingRequestFieldsAndMetadata() { + Map body = materialAndMetadata(); + body.remove("cert"); + Map original = new LinkedHashMap<>(body); + Map originalFields = new LinkedHashMap<>(fields(body)); + ApiRequest request = request(body); + RecordingNext next = new RecordingNext("update", request); + assertSame(next.result, new CertificateCreateValidationFilter().update("certificate", "1c42", request, next.manager)); + assertEquals(1, next.calls); + assertSame(body, request.getRequestObject()); + assertEquals(original, body); + assertEquals(originalFields, fields(body)); + assertFalse(body.containsKey("cert")); + } + + @Test + public void explicitNullCertificateUpdateStillRejectsBeforeNext() { + assertInvalid(false, true, null); + } + + @Test + public void explicitEmptyAndWhitespaceCertificateUpdateStillRejectsBeforeNext() { + assertInvalid(false, true, ""); + assertInvalid(false, true, " \r\n\t "); + } + + @Test + public void malformedCertificateUpdateStillRejectsBeforeNext() { + assertInvalid(false, true, "not a PEM certificate"); + assertInvalid(false, true, "-----BEGIN CERTIFICATE-----\nYWJj\n-----END CERTIFICATE-----"); + } + + @Test + public void validExplicitCertificateUpdateRecomputesAllDerivedFieldsAndPreservesOtherFields() throws Exception { + assertValid(false); + } + + @Test + public void validCreateStillParsesAllDerivedFieldsBeforeNext() throws Exception { + assertValid(true); + } + + @Test + public void createStillRejectsMissingNullEmptyWhitespaceAndMalformedCertificate() { + assertInvalid(true, false, null); + for (String value : new String[] { null, "", " \n\t ", "not a PEM certificate" }) { + assertInvalid(true, true, value); + } + } + + private static void assertOmittedCertForwarded(String key, String value) { + Map body = new LinkedHashMap<>(); + body.put(key, value); + Map original = new LinkedHashMap<>(body); + ApiRequest request = request(body); + RecordingNext next = new RecordingNext("update", request); + assertSame(next.result, new CertificateCreateValidationFilter().update("certificate", "1c42", request, next.manager)); + assertEquals(1, next.calls); + assertSame(body, request.getRequestObject()); + assertEquals(original, body); + for (String forbidden : Arrays.asList("cert", "key", "certChain", "data")) assertFalse(body.containsKey(forbidden)); + } + + private static void assertInvalid(boolean create, boolean present, String value) { + Map body = materialAndMetadata(); + if (present) body.put("cert", value); else body.remove("cert"); + Map before = new LinkedHashMap<>(fields(body)); + ApiRequest request = request(body); + RecordingNext next = new RecordingNext(create ? "create" : "update", request); + try { + CertificateCreateValidationFilter filter = new CertificateCreateValidationFilter(); + if (create) filter.create("certificate", request, next.manager); + else filter.update("certificate", "1c42", request, next.manager); + fail("Invalid or absent required certificate must not reach next"); + } catch (ClientVisibleException error) { + assertEquals(ResponseCodes.UNPROCESSABLE_ENTITY, error.getStatus()); + assertEquals(ValidationErrorCodes.INVALID_FORMAT, error.getCode()); + } + assertEquals(0, next.calls); + assertEquals(before, fields(body)); + assertEquals("synthetic-key-sentinel", body.get("key")); + assertEquals("synthetic-chain-sentinel", body.get("certChain")); + assertEquals("keep-description", body.get("description")); + } + + private static void assertValid(boolean create) throws Exception { + Map body = materialAndMetadata(); + body.put("cert", pem); + ApiRequest request = request(body); + RecordingNext next = new RecordingNext(create ? "create" : "update", request); + CertificateCreateValidationFilter filter = new CertificateCreateValidationFilter(); + Object actual = create ? filter.create("certificate", request, next.manager) + : filter.update("certificate", "1c42", request, next.manager); + assertSame(next.result, actual); + assertEquals(1, next.calls); + Certificate proxy = request.proxyRequestObject(Certificate.class); + Map expected = new LinkedHashMap<>(); + expected.put("certFingerprint", SslCertificateUtils.getCertificateFingerprint(pem)); + expected.put("expiresAt", SslCertificateUtils.getExpirationDate(pem)); + expected.put("CN", "certificate-regression.invalid"); + expected.put("issuer", SslCertificateUtils.getIssuer(pem)); + expected.put("issuedAt", SslCertificateUtils.getIssuedDate(pem)); + expected.put("version", "3"); + expected.put("algorithm", SslCertificateUtils.getAlgorithm(pem)); + expected.put("serialNumber", "42"); + expected.put("keySize", 2048); + expected.put("subjectAlternativeNames", Arrays.asList("certificate-regression.invalid")); + expected.put("unrelated", Arrays.asList("keep", "nested metadata")); + assertEquals(expected, DataUtils.getFields(proxy)); + assertEquals(pem, proxy.getCert()); + assertEquals("synthetic-key-sentinel", proxy.getKey()); + assertEquals("synthetic-chain-sentinel", proxy.getCertChain()); + assertEquals("keep-description", proxy.getDescription()); + assertEquals("keep-name", proxy.getName()); + assertEquals("keep-id", body.get("opaqueMarker")); + } + + private static Map materialAndMetadata() { + Map fields = new LinkedHashMap<>(); + for (String name : DERIVED) fields.put(name, "old-" + name); + fields.put("unrelated", Arrays.asList("keep", "nested metadata")); + Map data = new LinkedHashMap<>(); + data.put(DataUtils.FIELDS, fields); + Map body = new LinkedHashMap<>(); + body.put("name", "keep-name"); + body.put("description", "keep-description"); + body.put("key", "synthetic-key-sentinel"); + body.put("certChain", "synthetic-chain-sentinel"); + body.put("opaqueMarker", "keep-id"); + body.put("data", data); + return body; + } + + private static Map fields(Map body) { + Map data = CollectionUtils.castMap(body.get("data")); + return CollectionUtils.castMap(data.get(DataUtils.FIELDS)); + } + + private static ApiRequest request(Map body) { + ApiRequest request = new ApiRequest(null, null); + request.setRequestObject(body); + return request; + } + + private static final class RecordingNext { + final Object result = new Object(); + final ResourceManager manager; + int calls; + + RecordingNext(String operation, ApiRequest request) { + manager = ResourceManager.class.cast(Proxy.newProxyInstance(ResourceManager.class.getClassLoader(), + new Class[] { ResourceManager.class }, (proxy, method, arguments) -> { + assertEquals(operation, method.getName()); + assertEquals("certificate", arguments[0]); + if ("update".equals(operation)) assertEquals("1c42", arguments[1]); + assertSame(request, arguments[arguments.length - 1]); + calls++; + return result; + })); + } + } +} diff --git a/code/iaas/archaius-management/pom.xml b/code/iaas/archaius-management/pom.xml index bc62adfa35..255180e1ac 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/auth-logic/pom.xml b/code/iaas/auth-logic/pom.xml index 8220e26b49..308227f40f 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/bootstrap/pom.xml b/code/iaas/bootstrap/pom.xml index 9c194d1dd2..3d72dd67e5 100644 --- a/code/iaas/bootstrap/pom.xml +++ b/code/iaas/bootstrap/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/config-item/api/pom.xml b/code/iaas/config-item/api/pom.xml index d1039bead6..747c13c17c 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.326 + 0.183.327 ../../../parent/pom.xml diff --git a/code/iaas/config-item/common/pom.xml b/code/iaas/config-item/common/pom.xml index 992f2f9c67..4cf0d22421 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.326 + 0.183.327 ../../../parent/pom.xml diff --git a/code/iaas/config-item/server/pom.xml b/code/iaas/config-item/server/pom.xml index 9609bb9ed8..f6e8deedc1 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.326 + 0.183.327 ../../../parent/pom.xml diff --git a/code/iaas/engine-jooq/pom.xml b/code/iaas/engine-jooq/pom.xml index e5f43a8737..c72080380e 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/events/pom.xml b/code/iaas/events/pom.xml index 28385c8495..dac16a85c5 100644 --- a/code/iaas/events/pom.xml +++ b/code/iaas/events/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/external-handler/pom.xml b/code/iaas/external-handler/pom.xml index 4330f901e8..53122c7c3a 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/ha/pom.xml b/code/iaas/ha/pom.xml index 6d171b90b7..52dfbe94b6 100644 --- a/code/iaas/ha/pom.xml +++ b/code/iaas/ha/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/healthcheck/pom.xml b/code/iaas/healthcheck/pom.xml index 38a5f04662..9b166c96c6 100644 --- a/code/iaas/healthcheck/pom.xml +++ b/code/iaas/healthcheck/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/labels/pom.xml b/code/iaas/labels/pom.xml index 3a475fe07b..254f814924 100644 --- a/code/iaas/labels/pom.xml +++ b/code/iaas/labels/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/logic-common/pom.xml b/code/iaas/logic-common/pom.xml index 0b42e5c327..989ad15442 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/logic/pom.xml b/code/iaas/logic/pom.xml index 0f0f676b13..07f8b79698 100644 --- a/code/iaas/logic/pom.xml +++ b/code/iaas/logic/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/metadata/pom.xml b/code/iaas/metadata/pom.xml index c78a84faf0..497a0d5173 100644 --- a/code/iaas/metadata/pom.xml +++ b/code/iaas/metadata/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/model/pom.xml b/code/iaas/model/pom.xml index 5bab5ef3ff..5a9f2a3b2c 100644 --- a/code/iaas/model/pom.xml +++ b/code/iaas/model/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/resource-pool/pom.xml b/code/iaas/resource-pool/pom.xml index 8d3cf24766..eda57f52cd 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/service-discovery/api/pom.xml b/code/iaas/service-discovery/api/pom.xml index 8507534de8..c51ef923b8 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.326 + 0.183.327 ../../../parent/pom.xml diff --git a/code/iaas/service-discovery/api/src/main/java/io/cattle/platform/servicediscovery/api/filter/LoadBalancerServiceCertificateRemoveFilter.java b/code/iaas/service-discovery/api/src/main/java/io/cattle/platform/servicediscovery/api/filter/LoadBalancerServiceCertificateRemoveFilter.java index 35def7a4b5..8f0de6ff57 100644 --- a/code/iaas/service-discovery/api/src/main/java/io/cattle/platform/servicediscovery/api/filter/LoadBalancerServiceCertificateRemoveFilter.java +++ b/code/iaas/service-discovery/api/src/main/java/io/cattle/platform/servicediscovery/api/filter/LoadBalancerServiceCertificateRemoveFilter.java @@ -77,7 +77,7 @@ protected void validateIfCertificateInUse(String certificateId) { } List certIds = new ArrayList<>(); if (lbConfig.getCertificateIds() != null) { - certIds.addAll(certIds); + certIds.addAll(lbConfig.getCertificateIds()); } if (lbConfig.getDefaultCertificateId() != null) { certIds.add(lbConfig.getDefaultCertificateId()); diff --git a/code/iaas/service-discovery/api/src/test/java/io/cattle/platform/servicediscovery/api/filter/LoadBalancerServiceCertificateRemoveFilterTest.java b/code/iaas/service-discovery/api/src/test/java/io/cattle/platform/servicediscovery/api/filter/LoadBalancerServiceCertificateRemoveFilterTest.java new file mode 100644 index 0000000000..8d96bcbd1f --- /dev/null +++ b/code/iaas/service-discovery/api/src/test/java/io/cattle/platform/servicediscovery/api/filter/LoadBalancerServiceCertificateRemoveFilterTest.java @@ -0,0 +1,206 @@ +package io.cattle.platform.servicediscovery.api.filter; + +import static io.cattle.platform.core.model.tables.ServiceTable.SERVICE; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import io.cattle.platform.core.addon.LbConfig; +import io.cattle.platform.core.constants.ServiceConstants; +import io.cattle.platform.core.dao.ServiceDao; +import io.cattle.platform.core.model.Certificate; +import io.cattle.platform.core.model.Service; +import io.cattle.platform.core.model.tables.records.CertificateRecord; +import io.cattle.platform.core.model.tables.records.ServiceRecord; +import io.cattle.platform.json.JacksonJsonMapper; +import io.cattle.platform.object.ObjectManager; +import io.cattle.platform.object.util.DataUtils; +import io.cattle.platform.servicediscovery.api.service.ServiceDiscoveryApiService; +import io.github.ibuildthecloud.gdapi.exception.ClientVisibleException; +import io.github.ibuildthecloud.gdapi.request.ApiRequest; +import io.github.ibuildthecloud.gdapi.request.resource.ResourceManager; +import io.github.ibuildthecloud.gdapi.util.ResponseCodes; +import io.github.ibuildthecloud.gdapi.validation.ValidationErrorCodes; + +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +public class LoadBalancerServiceCertificateRemoveFilterTest { + @Test + public void v2AlternateReferenceBlocksDeleteAndRemoveEvenWithDifferentDefault() { + assertBoth(true, false, config(Arrays.asList(7L, 42L), 99L), Collections.emptyList(), null); + } + + @Test + public void v2DefaultReferenceBlocksDeleteAndRemove() { + assertBoth(true, false, config(Arrays.asList(7L), 42L), Collections.emptyList(), null); + } + + @Test + public void v2UnreferencedCertificateForwardsDeleteAndRemoveExactlyOnce() { + assertBoth(false, false, config(Arrays.asList(7L, 8L), 99L), Collections.emptyList(), null); + } + + @Test + public void v2MissingConfigurationAndNullOrEmptyListsStillPermitUnreferencedCertificate() { + assertBoth(false, false, null, Collections.emptyList(), null); + assertBoth(false, false, config(null, null), Collections.emptyList(), null); + assertBoth(false, false, config(Collections.emptyList(), null), Collections.emptyList(), null); + assertBoth(false, false, config(null, 99L), Collections.emptyList(), null); + } + + @Test + public void v2NullAlternateListStillProtectsDefaultReference() { + assertBoth(true, false, config(null, 42L), Collections.emptyList(), null); + } + + @Test + public void v1AlternateReferenceStillBlocksDeleteAndRemove() { + assertBoth(true, true, null, Arrays.asList(certificate(7L), certificate(42L)), certificate(99L)); + } + + @Test + public void v1DefaultReferenceStillBlocksDeleteAndRemove() { + assertBoth(true, true, null, Arrays.asList(certificate(7L)), certificate(42L)); + } + + @Test + public void v1NoReferenceAndMissingDefaultStillPermitDeleteAndRemove() { + assertBoth(false, true, null, Arrays.asList(certificate(7L)), certificate(99L)); + assertBoth(false, true, null, Collections.emptyList(), null); + } + + private static void assertBoth(boolean blocked, boolean v1, LbConfig configuration, + List alternate, Certificate defaultCertificate) { + for (boolean removeAction : new boolean[] { false, true }) { + Fixture fixture = new Fixture(v1, configuration, alternate, defaultCertificate, removeAction); + if (blocked) { + try { + fixture.call(); + fail("A referenced certificate must not reach the next deletion manager"); + } catch (ClientVisibleException error) { + assertEquals(ResponseCodes.METHOD_NOT_ALLOWED, error.getStatus()); + assertEquals(ValidationErrorCodes.INVALID_ACTION, error.getCode()); + assertTrue(error.getMessage().contains("test-lb")); + } + assertEquals(0, fixture.nextCalls); + } else { + assertSame(fixture.result, fixture.call()); + assertEquals(1, fixture.nextCalls); + } + assertEquals(1, fixture.loads); + assertEquals(1, fixture.finds); + assertEquals(1, fixture.v1Checks); + assertEquals(v1 ? 1 : 0, fixture.alternateReads); + assertEquals(v1 ? 1 : 0, fixture.defaultReads); + assertEquals(configuration, DataUtils.getFields(fixture.service).get(ServiceConstants.FIELD_LB_CONFIG)); + assertEquals("active", fixture.target.getState()); + assertEquals("synthetic-certificate-sentinel", fixture.target.getCert()); + assertEquals("synthetic-key-sentinel", fixture.target.getKey()); + } + } + + private static LbConfig config(List alternate, Long defaultId) { + LbConfig configuration = new LbConfig(); + configuration.setCertificateIds(alternate); + configuration.setDefaultCertificateId(defaultId); + return configuration; + } + + private static CertificateRecord certificate(long number) { + CertificateRecord certificate = new CertificateRecord(); + certificate.setId(number); + certificate.setAccountId(2515L); + certificate.setState("active"); + certificate.setCert("synthetic-certificate-sentinel"); + certificate.setKey("synthetic-key-sentinel"); + return certificate; + } + + private static final class Fixture { + final LoadBalancerServiceCertificateRemoveFilter filter = new LoadBalancerServiceCertificateRemoveFilter(); + final CertificateRecord target = certificate(42L); + final ServiceRecord service = new ServiceRecord(); + final ApiRequest request = new ApiRequest(null, null); + final Object result = new Object(); + final ResourceManager next; + final boolean removeAction; + int nextCalls, loads, finds, v1Checks, alternateReads, defaultReads; + + Fixture(boolean v1, LbConfig configuration, List alternate, + Certificate defaultCertificate, boolean removeAction) { + this.removeAction = removeAction; + service.setId(73L); + service.setName("test-lb"); + service.setAccountId(2515L); + service.setKind(ServiceConstants.KIND_LOAD_BALANCER_SERVICE); + service.setState("active"); + Map fields = new LinkedHashMap<>(); + fields.put(ServiceConstants.FIELD_LB_CONFIG, configuration); + Map data = new LinkedHashMap<>(); + data.put(DataUtils.FIELDS, fields); + service.setData(data); + filter.jsonMapper = new JacksonJsonMapper(); + filter.objectManager = ObjectManager.class.cast(Proxy.newProxyInstance(ObjectManager.class.getClassLoader(), + new Class[] { ObjectManager.class }, (proxy, method, arguments) -> { + if ("loadResource".equals(method.getName())) { + assertSame(Certificate.class, arguments[0]); + assertEquals("1c42", arguments[1]); + loads++; + return target; + } + assertEquals("find", method.getName()); + assertSame(Service.class, arguments[0]); + assertEquals(3, arguments.length); + assertSame(SERVICE.ACCOUNT_ID, arguments[1]); + assertArrayEquals(new Object[] { 2515L, SERVICE.REMOVED, null, + SERVICE.KIND, ServiceConstants.KIND_LOAD_BALANCER_SERVICE }, (Object[]) arguments[2]); + finds++; + return Arrays.asList(service); + })); + filter.sdService = ServiceDiscoveryApiService.class.cast(Proxy.newProxyInstance( + ServiceDiscoveryApiService.class.getClassLoader(), new Class[] { ServiceDiscoveryApiService.class }, + (proxy, method, arguments) -> { + assertEquals("isV1LB", method.getName()); + assertSame(service, arguments[0]); + v1Checks++; + return v1; + })); + filter.svcDao = ServiceDao.class.cast(Proxy.newProxyInstance(ServiceDao.class.getClassLoader(), + new Class[] { ServiceDao.class }, (proxy, method, arguments) -> { + assertSame(service, arguments[0]); + if ("getLoadBalancerServiceCertificates".equals(method.getName())) { + alternateReads++; + return alternate; + } + assertEquals("getLoadBalancerServiceDefaultCertificate", method.getName()); + defaultReads++; + return defaultCertificate; + })); + request.setId("1c42"); + request.setAction("ReMoVe"); + next = ResourceManager.class.cast(Proxy.newProxyInstance(ResourceManager.class.getClassLoader(), + new Class[] { ResourceManager.class }, (proxy, method, arguments) -> { + assertEquals(removeAction ? "resourceAction" : "delete", method.getName()); + assertEquals("certificate", arguments[0]); + if (!removeAction) assertEquals("1c42", arguments[1]); + assertSame(request, arguments[arguments.length - 1]); + nextCalls++; + return result; + })); + } + + Object call() { + return removeAction ? filter.resourceAction("certificate", request, next) + : filter.delete("certificate", "1c42", request, next); + } + } +} diff --git a/code/iaas/service-discovery/server/pom.xml b/code/iaas/service-discovery/server/pom.xml index d7d447b7c9..ee3745b380 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.326 + 0.183.327 ../../../parent/pom.xml diff --git a/code/iaas/ssh-common/pom.xml b/code/iaas/ssh-common/pom.xml index 183c18a145..3a19993824 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/storage-service/pom.xml b/code/iaas/storage-service/pom.xml index ef93307d14..83818707a1 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/iaas/task-jooq/pom.xml b/code/iaas/task-jooq/pom.xml index fa0f37ce86..d6873a78e2 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/implementation/activity-log/pom.xml b/code/implementation/activity-log/pom.xml index 67d4552f79..94a147b2d4 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/implementation/agent-instance-impl/pom.xml b/code/implementation/agent-instance-impl/pom.xml index b4b17acb3d..36c80ef81d 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.326 + 0.183.327 ../../parent/pom.xml cattle-agent-instance-impl diff --git a/code/implementation/docker/api/pom.xml b/code/implementation/docker/api/pom.xml index 55d60be83a..eeb938aae5 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.326 + 0.183.327 ../../../parent/pom.xml diff --git a/code/implementation/docker/common/pom.xml b/code/implementation/docker/common/pom.xml index d977d7041c..a13ed71cc7 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.326 + 0.183.327 ../../../parent/pom.xml diff --git a/code/implementation/docker/compute/pom.xml b/code/implementation/docker/compute/pom.xml index 46ba982ee7..ad857a172a 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.326 + 0.183.327 ../../../parent/pom.xml diff --git a/code/implementation/docker/machine/pom.xml b/code/implementation/docker/machine/pom.xml index 688cebc929..e32579c381 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.326 + 0.183.327 ../../../parent/pom.xml diff --git a/code/implementation/docker/storage/pom.xml b/code/implementation/docker/storage/pom.xml index 3c79935647..28a457b526 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.326 + 0.183.327 ../../../parent/pom.xml @@ -21,12 +21,12 @@ io.cattle cattle-docker-common - 0.183.326 + 0.183.327 io.cattle cattle-iaas-allocator - 0.183.326 + 0.183.327 diff --git a/code/implementation/extension-api/pom.xml b/code/implementation/extension-api/pom.xml index ca91fe5674..cccf48b638 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.326 + 0.183.327 ../../parent/pom.xml cattle-extension-api diff --git a/code/implementation/hazelcast/common/pom.xml b/code/implementation/hazelcast/common/pom.xml index 9adf303959..58c38f4a44 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.326 + 0.183.327 ../../../parent/pom.xml diff --git a/code/implementation/hazelcast/eventing/pom.xml b/code/implementation/hazelcast/eventing/pom.xml index 4032ff8f34..9508ab200d 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.326 + 0.183.327 ../../../parent/pom.xml diff --git a/code/implementation/hazelcast/lock/pom.xml b/code/implementation/hazelcast/lock/pom.xml index 519f8cff8c..e69a40b1a6 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.326 + 0.183.327 ../../../parent/pom.xml diff --git a/code/implementation/host-api/pom.xml b/code/implementation/host-api/pom.xml index e822a9045e..d67abfac3c 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/implementation/host-stats/pom.xml b/code/implementation/host-stats/pom.xml index 7744b01066..9802e05fad 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/implementation/register/pom.xml b/code/implementation/register/pom.xml index cb97d393af..a825b2aeb7 100644 --- a/code/implementation/register/pom.xml +++ b/code/implementation/register/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/implementation/sample-setup/pom.xml b/code/implementation/sample-setup/pom.xml index 9afc3eb7b1..461ec04a0e 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/implementation/settings-api/pom.xml b/code/implementation/settings-api/pom.xml index 66218461cd..6c04deb401 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/implementation/simulator/agent-connection/pom.xml b/code/implementation/simulator/agent-connection/pom.xml index 2a01ca92bd..9c151a0340 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.326 + 0.183.327 ../../../parent/pom.xml diff --git a/code/implementation/simulator/storage/pom.xml b/code/implementation/simulator/storage/pom.xml index cb577da372..2c5b3b8f47 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.326 + 0.183.327 ../../../parent/pom.xml diff --git a/code/implementation/system-stack/pom.xml b/code/implementation/system-stack/pom.xml index 4bdeb812b2..f04f976c6f 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.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/implementation/vm/pom.xml b/code/implementation/vm/pom.xml index b0da0dfc74..6b9a39aefc 100644 --- a/code/implementation/vm/pom.xml +++ b/code/implementation/vm/pom.xml @@ -3,7 +3,7 @@ cattle-parent io.cattle - 0.183.326 + 0.183.327 ../../parent/pom.xml cattle-vm diff --git a/code/meta-parent/pom.xml b/code/meta-parent/pom.xml index 2b594926c0..5b09ae6034 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.326 + 0.183.327 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 c7ee5a2710..95f715877d 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.326 + 0.183.327 ../../parent/pom.xml jar diff --git a/code/packaging/app/pom.xml b/code/packaging/app/pom.xml index 7c92bcfb93..2d56769482 100644 --- a/code/packaging/app/pom.xml +++ b/code/packaging/app/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.326 + 0.183.327 ../../parent/pom.xml war diff --git a/code/packaging/bundle/pom.xml b/code/packaging/bundle/pom.xml index ac17d0a250..2dca67f431 100644 --- a/code/packaging/bundle/pom.xml +++ b/code/packaging/bundle/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/packaging/dev/pom.xml b/code/packaging/dev/pom.xml index 74bbd8aa2e..3318684384 100644 --- a/code/packaging/dev/pom.xml +++ b/code/packaging/dev/pom.xml @@ -4,7 +4,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/packaging/meta/pom.xml b/code/packaging/meta/pom.xml index fdd71e7945..0505f68506 100644 --- a/code/packaging/meta/pom.xml +++ b/code/packaging/meta/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.326 + 0.183.327 ../../parent/pom.xml diff --git a/code/parent/pom.xml b/code/parent/pom.xml index 40e0129905..89eb2cf98b 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.326 + 0.183.327 pom diff --git a/docs/releases/orchestration-engine-0.183.327.md b/docs/releases/orchestration-engine-0.183.327.md new file mode 100644 index 0000000000..b4b11bc12f --- /dev/null +++ b/docs/releases/orchestration-engine-0.183.327.md @@ -0,0 +1,52 @@ +# Orchestration Engine 0.183.327 + +This release fixes two shared Certificate API lifecycle paths. It preserves +the authorization, private-key masking, and v1 schema changes from 0.183.326. + +## Partial updates + +The Certificate update filter previously parsed `cert` even when a request +omitted that field. Name-only and description-only updates therefore failed +with HTTP 422. The filter now derives certificate metadata only when the +request explicitly contains `cert`. Explicit null, empty, or malformed values +still fail with HTTP 422 / InvalidFormat, and create validation is unchanged. +An omitted certificate or private key is not fetched and resubmitted by the +filter. No schema, authentication, or private-key output policy is changed. + +## Load balancer reference protection + +The shared DELETE / remove-action guard now reads the actual alternate IDs +from a v2 `lbConfig.certificateIds` list, rather than adding an empty local +list to itself. Alternate and default references in non-removed load balancer +services in the certificate's account block removal with the existing +HTTP 405 / InvalidAction response. The v1 helper path and the query's +account, removed, and kind constraints are preserved. Unused certificates +can still be deleted. Account teardown process behavior is not changed. + +## Focused regression coverage and runtime acceptance + +The Dapper builder retains its base package snapshot and direct tool versions. +After that install it consumes a signed `20260930T000000Z` Ubuntu +`resolute-security` snapshot, exact-pinning `libssl3t64`, `openssl` and +`openssl-provider-legacy` to `3.5.5-1ubuntu3.6`. This is the official Ubuntu +[CVE-2026-84782 fix](https://ubuntu.com/security/notices/USN-8847-1), on the +existing OpenSSL 3.5 line. Package inventory records both snapshots. No +High/Critical exemption or security-gate relaxation is added. + +Two JUnit4 classes exercise the real API filters. Nine partial-update/create +tests cover omitted, explicit invalid, and valid certificate input and the +ten derived metadata fields. Eight load-balancer tests each exercise DELETE +and the case-insensitive remove action, including alternate/default, v1/v2, +and unreferenced inputs. Downstream calls and query boundaries are asserted. +Restoring just the two old implementations makes three omitted-field update +tests fail with InvalidFormat and the v2 alternate-reference test reach the +downstream deletion manager. The patched filters pass all 17 focused tests. +The complete JDK25 package gate and release-artifact checks remain required. + +Server acceptance must independently verify both v1 and v2-beta name and +description edits on fresh, unmounted certificates, unchanged certificate/key +storage, native UI save/cancel/readback, and denied removal of referenced +alternate/default certificates. Focused tests alone are not full platform QA. + +No database migration is required. Rollback to 0.183.326 restores both bugs; +retain the previous immutable Server image and the existing named volumes. diff --git a/pom.xml b/pom.xml index b7b87e8082..ef12e1d0c3 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ io.cattle cattle-parent - 0.183.326 + 0.183.327 code/parent/pom.xml cattle diff --git a/resources/pom.xml b/resources/pom.xml index 1759b8b60e..4c0d6c3df8 100644 --- a/resources/pom.xml +++ b/resources/pom.xml @@ -4,7 +4,7 @@ cattle-parent io.cattle - 0.183.326 + 0.183.327 ../code/parent/pom.xml diff --git a/scripts/build b/scripts/build index 57dee236e9..e522a06c40 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.326} +ENGINE_VERSION=${ENGINE_VERSION:-0.183.327} case "$SOURCE_REVISION" in ''|*[!0-9a-f]*) diff --git a/scripts/check-pasturestack-source b/scripts/check-pasturestack-source index 9b795d5765..9bc49727b4 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.326' +require_line code/meta-parent/pom.xml ' 0.183.327' 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' @@ -64,6 +64,8 @@ require_line Dockerfile.dapper 'ARG TEMURIN_JDK25_SHA256=e58fcdcd637b25c03ca84cb require_line Dockerfile 'ARG MAVEN_VERSION=3.9.16' require_line Dockerfile.dapper 'ARG MAVEN_VERSION=3.9.16' require_line ubuntu-apt.lock "UBUNTU_APT_SNAPSHOT='20260826T000000Z'" +require_line ubuntu-apt.lock "UBUNTU_APT_SECURITY_SNAPSHOT='20260930T000000Z'" +require_line ubuntu-apt.lock "UBUNTU_APT_OPENSSL_VERSION='3.5.5-1ubuntu3.6'" require_line ubuntu-apt.lock "UBUNTU_APT_CURL_VERSION='8.18.0-1ubuntu2.4'" require_line ubuntu-apt.lock "UBUNTU_APT_PYTHON3_14_VERSION='3.14.4-1ubuntu0.1'" require_line Dockerfile ' rm -f /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources; \' @@ -408,4 +410,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.326 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.4 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.327 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.4 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 a8d86e08f1..69851a94c3 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.326} +expected_version=${EXPECTED_ENGINE_VERSION:-0.183.327} test -f "$artifact" artifact=$(realpath "$artifact") diff --git a/tests/integration/cattletest/core/test_balancer_svc.py b/tests/integration/cattletest/core/test_balancer_svc.py index 351e1bb395..8a984caf7b 100644 --- a/tests/integration/cattletest/core/test_balancer_svc.py +++ b/tests/integration/cattletest/core/test_balancer_svc.py @@ -124,6 +124,12 @@ def test_validate_balancer_svc_fields(client, image_uuid): assert e.value.error.status == 405 assert e.value.error.code == 'InvalidAction' + # Alternate certificates must have the same protection as the default. + with pytest.raises(ApiError) as e: + cert2.remove() + assert e.value.error.status == 405 + assert e.value.error.code == 'InvalidAction' + # delete balancer service client.wait_success(lb_svc.remove()) cert1.remove() diff --git a/ubuntu-apt.lock b/ubuntu-apt.lock index 2972bbb6fd..cc98579a73 100644 --- a/ubuntu-apt.lock +++ b/ubuntu-apt.lock @@ -1,7 +1,10 @@ # Ubuntu 26.04 package lock for reproducible PastureStack builds. -# Refresh the snapshot and every exact direct-package version together. +# Refresh the base snapshot and its exact direct-package versions together. +# Security overlays are added after the base install and pin only listed fixes. UBUNTU_APT_SNAPSHOT='20260826T000000Z' +UBUNTU_APT_SECURITY_SNAPSHOT='20260930T000000Z' +UBUNTU_APT_OPENSSL_VERSION='3.5.5-1ubuntu3.6' UBUNTU_APT_BASH_VERSION='5.3-2ubuntu1' UBUNTU_APT_CA_CERTIFICATES_VERSION='20260601~26.04.1'