This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 156f8d62b fix(k8s): normalize certificate identities on create like on
update (#4300)
156f8d62b is described below
commit 156f8d62b3ac464f1da7602e4eb90ed68b224c1e
Author: btlqql <[email protected]>
AuthorDate: Tue Sep 15 21:08:36 2026 +0800
fix(k8s): normalize certificate identities on create like on update (#4300)
createCert stored k8sId, cluster and issuer verbatim, so a whitespace-only
issuer was accepted and padded identities were persisted, while updateCert
trims them and rejects a blank value.
(cherry picked from commit 891344f37e7445556ea8c9dddaf7da99a1ea09d2)
---
.../studio/cluster/k8s/K8sCertService.java | 16 ++++++---
.../studio/cluster/k8s/K8sCertServiceTest.java | 39 ++++++++++++++++++++++
2 files changed, 51 insertions(+), 4 deletions(-)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertService.java
b/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertService.java
index dfa541627..ce7109a61 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertService.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertService.java
@@ -78,9 +78,17 @@ public class K8sCertService {
LocalDateTime now = LocalDateTime.now(clock);
LocalDateTime notBefore = now;
LocalDateTime notAfter = now.plusYears(1);
- String issuer = command.getIssuer();
+ // The identity fields follow the same rules as updateCert:
surrounding whitespace is trimmed
+ // and a blank value is rejected, so a certificate cannot be created
in a state that the
+ // update API (and every later lookup by identity) refuses to produce.
+ String k8sId = normalizeOptionalIdentity(command.getK8sId(), "k8sId");
+ String cluster = normalizeOptionalIdentity(command.getCluster(),
"cluster");
+ boolean certificateSupplied = command.getCertPem() != null &&
!command.getCertPem().isBlank();
+ // A supplied certificate always wins over the submitted issuer, so
only a create without one
+ // validates the caller-provided value.
+ String issuer = certificateSupplied ? null :
normalizeOptionalIdentity(command.getIssuer(), "issuer");
List<String> san = command.getSan();
- if (command.getCertPem() != null && !command.getCertPem().isBlank()) {
+ if (certificateSupplied) {
X509Certificate parsed = parseCertificate(command.getCertPem());
notBefore =
LocalDateTime.ofInstant(parsed.getNotBefore().toInstant(),
ZoneId.systemDefault());
notAfter =
LocalDateTime.ofInstant(parsed.getNotAfter().toInstant(),
ZoneId.systemDefault());
@@ -89,8 +97,8 @@ public class K8sCertService {
}
K8sCertVO cert = K8sCertVO.builder()
- .k8sId(command.getK8sId())
- .cluster(command.getCluster())
+ .k8sId(k8sId)
+ .cluster(cluster)
.type(type)
.issuer(issuer)
.notBefore(notBefore)
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertServiceTest.java
b/server/src/test/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertServiceTest.java
index 39d55cd24..022774bb4 100644
---
a/server/src/test/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertServiceTest.java
+++
b/server/src/test/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertServiceTest.java
@@ -197,6 +197,45 @@ class K8sCertServiceTest {
verifyNoInteractions(k8sCertRepository);
}
+ @Test
+ void createCertShouldRejectBlankIdentityFields() {
+ // updateCert refuses a blank identity, so createCert must not accept
one either: the
+ // certificate would otherwise be stored in a state the update API
cannot produce.
+ List<Map.Entry<String, Consumer<CreateCertDTO>>> invalidCreates =
List.of(
+ Map.entry("k8sId", command -> command.setK8sId(" ")),
+ Map.entry("cluster", command -> command.setCluster("\n")),
+ Map.entry("issuer", command -> command.setIssuer(" ")));
+
+ for (Map.Entry<String, Consumer<CreateCertDTO>> invalidCreate :
invalidCreates) {
+ CreateCertDTO command =
CreateCertDTO.builder().type("TLS").build();
+ invalidCreate.getValue().accept(command);
+
+ assertThatThrownBy(() -> k8sCertService.createCert(command))
+ .isInstanceOf(BusinessException.class)
+ .hasMessage("Certificate " + invalidCreate.getKey() + "
cannot be blank")
+ .satisfies(error -> assertThat(((BusinessException)
error).getCode()).isEqualTo(400));
+ }
+
+ verify(k8sCertRepository, never()).save(any(K8sCertVO.class));
+ }
+
+ @Test
+ void createCertShouldTrimIdentityFieldsLikeUpdateDoes() {
+ CreateCertDTO command = CreateCertDTO.builder()
+ .k8sId(" new-tls-cert ")
+ .cluster(" test-cluster ")
+ .type("TLS")
+ .issuer(" vault ")
+ .build();
+
when(k8sCertRepository.save(any(K8sCertVO.class))).thenAnswer(invocation ->
invocation.getArgument(0));
+
+ K8sCertVO result = k8sCertService.createCert(command);
+
+ assertThat(result.getK8sId()).isEqualTo("new-tls-cert");
+ assertThat(result.getCluster()).isEqualTo("test-cluster");
+ assertThat(result.getIssuer()).isEqualTo("vault");
+ }
+
@Test
void createCertShouldRejectInvalidTypeBeforeSave() {
CreateCertDTO command = CreateCertDTO.builder()