Skip to content

Commit 66f2643

Browse files
committed
[#2819] Prevent tenants from having empty CA array
The (partial) unique index defined on the tenant's trusted-ca array is defined using an $exists expression on the trusted-ca property. This causes tenants with an empty trusted-ca array to also be indexed. The Tenant class' trustedCertificateAuthorities field has been annotated to only be included in its JSON representation when the collection is not empty. Fixes #2819 Conflicts: site/homepage/content/release-notes.md Signed-off-by: Kai Hudalla <[email protected]>
1 parent 499c8dc commit 66f2643

File tree

4 files changed

+86
-4
lines changed

4 files changed

+86
-4
lines changed

services/device-registry-base/src/main/java/org/eclipse/hono/service/management/tenant/Tenant.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,12 @@ public class Tenant {
7373
private ResourceLimits resourceLimits;
7474

7575
@JsonProperty(RegistryManagementConstants.FIELD_TRACING)
76-
@JsonInclude(Include.NON_EMPTY)
76+
@JsonInclude(Include.NON_NULL)
7777
private TenantTracingConfig tracing;
7878

7979
@JsonProperty(RegistryManagementConstants.FIELD_PAYLOAD_TRUSTED_CA)
80-
private List<TrustedCertificateAuthority> trustedCertificateAuthorities;
80+
@JsonInclude(Include.NON_EMPTY)
81+
private List<TrustedCertificateAuthority> trustedCertificateAuthorities = List.of();
8182

8283
/**
8384
* Creates a new Tenant instance.
@@ -108,7 +109,7 @@ public Tenant(final Tenant other) {
108109
this.resourceLimits = other.resourceLimits;
109110
this.tracing = other.tracing;
110111
if (Objects.nonNull(other.trustedCertificateAuthorities)) {
111-
this.trustedCertificateAuthorities = new ArrayList<>(other.trustedCertificateAuthorities);
112+
this.trustedCertificateAuthorities = List.copyOf(other.trustedCertificateAuthorities);
112113
}
113114
}
114115

@@ -367,7 +368,7 @@ public List<TrustedCertificateAuthority> getTrustedCertificateAuthorities() {
367368
*/
368369
public Tenant setTrustedCertificateAuthorities(final List<TrustedCertificateAuthority> trustedCertificateAuthorities) {
369370
if (trustedCertificateAuthorities != null) {
370-
this.trustedCertificateAuthorities = Collections.unmodifiableList(trustedCertificateAuthorities);
371+
this.trustedCertificateAuthorities = List.copyOf(trustedCertificateAuthorities);
371372
}
372373
return this;
373374
}

services/device-registry-base/src/test/java/org/eclipse/hono/service/tenant/AbstractTenantServiceTest.java

+46
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,52 @@ default void testUpdateTenantFailsForDuplicateCa(final VertxTestContext ctx) {
563563
}));
564564
}
565565

566+
/**
567+
* Verifies that setting an empty list of trusted CAs on multiple tenants does not result in a unique key
568+
* violation.
569+
*
570+
* @param ctx The vert.x test context.
571+
*/
572+
@Test
573+
default void testUpdateTenantPreventsEmptyCaArray(final VertxTestContext ctx) {
574+
575+
final var trustedCaOne = new TrustedCertificateAuthority();
576+
trustedCaOne.setSubjectDn("CN=one");
577+
trustedCaOne.setPublicKey("NOTAKEY".getBytes(StandardCharsets.UTF_8));
578+
579+
final var trustedCaTwo = new TrustedCertificateAuthority();
580+
trustedCaTwo.setSubjectDn("CN=two");
581+
trustedCaTwo.setPublicKey("NOTAKEY".getBytes(StandardCharsets.UTF_8));
582+
583+
// GIVEN two tenants with one CA configured each
584+
addTenant("tenantOne", new Tenant().setTrustedCertificateAuthorities(List.of(trustedCaOne)))
585+
.onFailure(ctx::failNow)
586+
.compose(ok -> addTenant("tenantTwo", new Tenant().setTrustedCertificateAuthorities(List.of(trustedCaTwo))))
587+
.onFailure(ctx::failNow)
588+
.compose(ok -> {
589+
// WHEN setting an empty list of trusted CAs on the first tenant
590+
final var updatedTenantOne = new Tenant().setTrustedCertificateAuthorities(List.of());
591+
return getTenantManagementService().updateTenant(
592+
"tenantOne",
593+
updatedTenantOne,
594+
Optional.empty(),
595+
NoopSpan.INSTANCE);
596+
})
597+
// THEN the update succeeds
598+
.onFailure(ctx::failNow)
599+
.compose(ok -> {
600+
// and WHEN setting an empty list of trusted CAs on the second tenant
601+
final var updatedTenantTwo = new Tenant().setTrustedCertificateAuthorities(List.of());
602+
return getTenantManagementService().updateTenant(
603+
"tenantTwo",
604+
updatedTenantTwo,
605+
Optional.empty(),
606+
NoopSpan.INSTANCE);
607+
})
608+
// THEN the update succeeds as well
609+
.onComplete(ctx.completing());
610+
}
611+
566612
/**
567613
* Verifies that a tenant is registered.
568614
*

site/homepage/content/release-notes.md

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ description = "Information about changes in recent Hono releases. Includes new f
1313
client certificate.
1414
* The Mongo DB based registry implementation now uses a proper DB index to find credentials by type and authentication
1515
ID. This will speed up query execution significantly when there are a lot of devices registered for a tenant.
16+
* The MongoDB based registry erroneously rejected requests that would result in multiple tenants having an empty
17+
set of trusted CAs. This has been fixed.
1618

1719
## 1.8.2
1820

tests/src/test/java/org/eclipse/hono/tests/registry/TenantManagementIT.java

+33
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,39 @@ public void testUpdateTenantSucceedsForConfigurationWithMissingTrustAnchorIds(fi
391391
}));
392392
}
393393

394+
/**
395+
* Verifies that setting an empty list of trusted CAs on multiple tenants does not result in a unique key
396+
* violation.
397+
*
398+
* @param context The Vert.x test context.
399+
*/
400+
@Test
401+
public void testUpdateTenantPreventsEmptyCaArray(final VertxTestContext context) {
402+
403+
final var tenantTwoId = getHelper().getRandomTenantId();
404+
final PublicKey publicKey = TenantApiTests.getRandomPublicKey();
405+
final TrustedCertificateAuthority trustAnchor1 = Tenants
406+
.createTrustAnchor("test-ca", "CN=test-dn-1", publicKey.getEncoded(), publicKey.getAlgorithm(),
407+
Instant.now(), Instant.now().plus(365, ChronoUnit.DAYS));
408+
final TrustedCertificateAuthority trustAnchor2 = Tenants
409+
.createTrustAnchor(null, "CN=test-dn-2", publicKey.getEncoded(), publicKey.getAlgorithm(),
410+
Instant.now(), Instant.now().plus(365, ChronoUnit.DAYS));
411+
412+
getHelper().registry.addTenant(tenantId, new Tenant().setTrustedCertificateAuthorities(List.of(trustAnchor1)))
413+
.compose(ok -> getHelper().registry.addTenant(
414+
tenantTwoId,
415+
new Tenant().setTrustedCertificateAuthorities(List.of(trustAnchor2))))
416+
.compose(ok -> getHelper().registry.updateTenant(
417+
tenantId,
418+
new Tenant().setTrustedCertificateAuthorities(List.of()),
419+
HttpURLConnection.HTTP_NO_CONTENT))
420+
.compose(ok -> getHelper().registry.updateTenant(
421+
tenantTwoId,
422+
new Tenant().setTrustedCertificateAuthorities(List.of()),
423+
HttpURLConnection.HTTP_NO_CONTENT))
424+
.onComplete(context.completing());
425+
}
426+
394427
/**
395428
* Verifies that the service rejects an update request for a non-existing tenant.
396429
*

0 commit comments

Comments
 (0)