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'