From 5b6cf99b9cd9d7b9ade2f181e494d712fd062895 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 9 Sep 2024 10:55:28 +0200 Subject: [PATCH] DNS TXT Record Verification using a random string --- .../HsDomainSetupBookingItemValidator.java | 33 ++++++++++++++----- .../HsDomainSetupHostingAssetValidator.java | 6 ++-- ...mainSetupBookingItemValidatorUnitTest.java | 2 +- ...ainSetupHostingAssetValidatorUnitTest.java | 10 +++--- 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsDomainSetupBookingItemValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsDomainSetupBookingItemValidator.java index d540ca16..664e7485 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsDomainSetupBookingItemValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsDomainSetupBookingItemValidator.java @@ -1,13 +1,14 @@ package net.hostsharing.hsadminng.hs.booking.item.validators; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItem; +import net.hostsharing.hsadminng.hs.validation.PropertiesProvider; import net.hostsharing.hsadminng.mapper.Array; +import jakarta.persistence.EntityManager; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; -import static net.hostsharing.hsadminng.hash.HashGenerator.Algorithm.LINUX_YESCRYPT; -import static net.hostsharing.hsadminng.hs.validation.PasswordProperty.passwordProperty; import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringProperty; class HsDomainSetupBookingItemValidator extends HsBookingItemEntityValidator { @@ -27,7 +28,7 @@ class HsDomainSetupBookingItemValidator extends HsBookingItemEntityValidator { "(co|ne|or|go|re|pe)\\.kr" ); public static final String DOMAIN_NAME_PROPERTY_NAME = "domainName"; - public static final String VERIFICATION_PASSPHRASE_PROPERTY_NAME = "verificationPassphrase"; + public static final String VERIFICATION_CODE_PROPERTY_NAME = "verificationCode"; HsDomainSetupBookingItemValidator() { super( @@ -35,8 +36,9 @@ class HsDomainSetupBookingItemValidator extends HsBookingItemEntityValidator { .matchesRegEx(FQDN_REGEX).describedAs("is not a (non-top-level) fully qualified domain name") .notMatchesRegEx(REGISTRAR_LEVEL_DOMAINS).describedAs("is a forbidden registrar-level domain name") .required(), - passwordProperty(VERIFICATION_PASSPHRASE_PROPERTY_NAME).minLength(8).maxLength(64) - .hashedUsing(LINUX_YESCRYPT).writeOnly().optional() + stringProperty(VERIFICATION_CODE_PROPERTY_NAME) + .readOnly().initializedBy(HsDomainSetupBookingItemValidator::generateVerificationCode) + ); } @@ -44,11 +46,26 @@ class HsDomainSetupBookingItemValidator extends HsBookingItemEntityValidator { public List validateEntity(final HsBookingItem bookingItem) { final var violations = new ArrayList(); final var domainName = bookingItem.getDirectValue(DOMAIN_NAME_PROPERTY_NAME, String.class); - if ( !bookingItem.isLoaded() && - domainName.matches("hostsharing.(com|net|org|coop)") ) { - violations.add("'" + bookingItem.toShortString() + ".resources." + DOMAIN_NAME_PROPERTY_NAME + "' = '" + domainName + "' is a forbidden Hostsharing domain name"); + if (!bookingItem.isLoaded() && + domainName.matches("hostsharing.(com|net|org|coop)")) { + violations.add("'" + bookingItem.toShortString() + ".resources." + DOMAIN_NAME_PROPERTY_NAME + "' = '" + domainName + + "' is a forbidden Hostsharing domain name"); } violations.addAll(super.validateEntity(bookingItem)); return violations; } + + private static String generateVerificationCode(final EntityManager em, final PropertiesProvider propertiesProvider) { + final var alphaNumeric = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; + final var secureRandom = new SecureRandom(); + final var sb = new StringBuilder(); + for (int i = 0; i < 40; ++i) { + if ( i > 0 && i % 4 == 0 ) { + sb.append("-"); + } + sb.append(alphaNumeric.charAt(secureRandom.nextInt(alphaNumeric.length()))); + } + return sb.toString(); + } + } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidator.java index 2eb52144..8d5cf2e4 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsDomainSetupHostingAssetValidator.java @@ -50,10 +50,10 @@ class HsDomainSetupHostingAssetValidator extends HostingAssetEntityValidator { final var result = new Dns(assetEntity.getIdentifier()).fetchRecordsOfType("TXT"); switch ( result.status() ) { case Dns.Status.SUCCESS: - final var hash = assetEntity.getBookingItem().getDirectValue("verificationPassphrase", String.class); - final var found = result.records().stream().filter(r -> r.contains("Hostsharing-domain-setup-verification-code=" + hash)).findAny(); + final var code = assetEntity.getBookingItem().getDirectValue("verificationCode", String.class); + final var found = result.records().stream().filter(r -> r.contains("Hostsharing-domain-setup-verification-code=" + code)).findAny(); if (found.isEmpty()) { - violations.add("[DNS] no TXT record 'Hostsharing-domain-setup-verification=...' with valid hash found for domain name '" + assetEntity.getIdentifier() + "'"); + violations.add("[DNS] no TXT record 'Hostsharing-domain-setup-verification="+code+"' with valid hash found for domain name '" + assetEntity.getIdentifier() + "'"); } break; diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsDomainSetupBookingItemValidatorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsDomainSetupBookingItemValidatorUnitTest.java index 4b48f32a..1c7758cc 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsDomainSetupBookingItemValidatorUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/validators/HsDomainSetupBookingItemValidatorUnitTest.java @@ -130,6 +130,6 @@ class HsDomainSetupBookingItemValidatorUnitTest { // then assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( "{type=string, propertyName=domainName, matchesRegEx=[^((?!-)[A-Za-z0-9-]{1,63}(?(ofEntries( - entry("domainName", domainName), - entry("verificationPassphrase", "some secret verification passphrase") + entry("domainName", domainName) ))) .build(); HsBookingItemEntityValidatorRegistry.forType(HsBookingItemType.DOMAIN_SETUP).prepareProperties(null, bookingItem); @@ -132,7 +131,7 @@ class HsDomainSetupHostingAssetValidatorUnitTest { final var result = validator.validateEntity(domainSetupHostingAssetEntity); // then - assertThat(result).containsExactlyInAnyOrder( + assertThat(result).contains( "'DOMAIN_SETUP:example.org.bookingItem' or parentItem must be null but is of type CLOUD_SERVER", "'DOMAIN_SETUP:example.org.parentAsset' must be null or of type DOMAIN_SETUP but is of type CLOUD_SERVER", "'DOMAIN_SETUP:example.org.assignedToAsset' must be null but is of type MANAGED_SERVER"); @@ -260,6 +259,7 @@ class HsDomainSetupHostingAssetValidatorUnitTest { // given final var domainSetupHostingAssetEntity = validEntityBuilder().build(); final var domainName = domainSetupHostingAssetEntity.getIdentifier(); + final var expectedHash = domainSetupHostingAssetEntity.getBookingItem().getDirectValue("verificationCode", String.class); Dns.fakeResultForDomain( domainName, Dns.Result.fromRecords("Hostsharing-domain-setup-verification-code=SOME-DEFINITELY-WRONG-HASH")); @@ -269,7 +269,7 @@ class HsDomainSetupHostingAssetValidatorUnitTest { final var result = validator.validateEntity(domainSetupHostingAssetEntity); // then - assertThat(result).contains("[DNS] no TXT record 'Hostsharing-domain-setup-verification=...' with valid hash found for domain name 'example.org'"); + assertThat(result).contains("[DNS] no TXT record 'Hostsharing-domain-setup-verification=" + expectedHash + "' with valid hash found for domain name 'example.org'"); } @Test @@ -278,7 +278,7 @@ class HsDomainSetupHostingAssetValidatorUnitTest { // given final var domainSetupHostingAssetEntity = validEntityBuilder().build(); final var domainName = domainSetupHostingAssetEntity.getIdentifier(); - final var expectedHash = domainSetupHostingAssetEntity.getBookingItem().getDirectValue("verificationPassphrase", String.class); + final var expectedHash = domainSetupHostingAssetEntity.getBookingItem().getDirectValue("verificationCode", String.class); Dns.fakeResultForDomain( domainName, Dns.Result.fromRecords("Hostsharing-domain-setup-verification-code=" + expectedHash));